File indexing completed on 2024-04-28 03:56:26

0001 /*
0002     This file is part of KNewStuff2.
0003     SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org>
0004     SPDX-FileCopyrightText: 2003-2007 Josef Spillner <spillner@kde.org>
0005     SPDX-FileCopyrightText: 2009 Frederik Gladhorn <gladhorn@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-or-later
0008 */
0009 
0010 #include "entry.h"
0011 
0012 #include <QDomElement>
0013 #include <QMetaEnum>
0014 #include <QStringList>
0015 #include <QXmlStreamReader>
0016 #include <knewstuffcore_debug.h>
0017 
0018 #include "xmlloader_p.h"
0019 
0020 using namespace KNSCore;
0021 
0022 class KNSCore::EntryPrivate : public QSharedData
0023 {
0024 public:
0025     EntryPrivate()
0026     {
0027         qRegisterMetaType<KNSCore::Entry::List>();
0028     }
0029 
0030     bool operator==(const EntryPrivate &other) const
0031     {
0032         return mUniqueId == other.mUniqueId && mProviderId == other.mProviderId;
0033     }
0034 
0035     QString mUniqueId;
0036     QString mRequestedUniqueId; // We need to map the entry to the request in the ResultsStream, but invalid entries would have an empty ID
0037     QString mName;
0038     QUrl mHomepage;
0039     QString mCategory;
0040     QString mLicense;
0041     QString mVersion;
0042     QDate mReleaseDate = QDate::currentDate();
0043 
0044     // Version and date if a newer version is found (updateable)
0045     QString mUpdateVersion;
0046     QDate mUpdateReleaseDate;
0047 
0048     Author mAuthor;
0049     int mRating = 0;
0050     int mNumberOfComments = 0;
0051     int mDownloadCount = 0;
0052     int mNumberFans = 0;
0053     int mNumberKnowledgebaseEntries = 0;
0054     QString mKnowledgebaseLink;
0055     QString mSummary;
0056     QString mShortSummary;
0057     QString mChangelog;
0058     QString mPayload;
0059     QStringList mInstalledFiles;
0060     QString mProviderId;
0061     QStringList mUnInstalledFiles;
0062     QString mDonationLink;
0063     QStringList mTags;
0064 
0065     QString mChecksum;
0066     QString mSignature;
0067     KNSCore::Entry::Status mStatus = Entry::Invalid;
0068     Entry::Source mSource = Entry::Online;
0069     Entry::EntryType mEntryType = Entry::CatalogEntry;
0070 
0071     QString mPreviewUrl[6];
0072     QImage mPreviewImage[6];
0073 
0074     QList<Entry::DownloadLinkInformation> mDownloadLinkInformationList;
0075 };
0076 
0077 Entry::Entry()
0078     : d(new EntryPrivate())
0079 {
0080 }
0081 
0082 Entry::Entry(const Entry &other)
0083     : d(other.d)
0084 {
0085 }
0086 
0087 Entry &Entry::operator=(const Entry &other)
0088 {
0089     d = other.d;
0090     return *this;
0091 }
0092 
0093 bool Entry::operator<(const KNSCore::Entry &other) const
0094 {
0095     return d->mUniqueId < other.d->mUniqueId;
0096 }
0097 
0098 bool Entry::operator==(const KNSCore::Entry &other) const
0099 {
0100     return d->mUniqueId == other.d->mUniqueId && d->mProviderId == other.d->mProviderId;
0101 }
0102 
0103 Entry::~Entry() = default;
0104 
0105 bool Entry::isValid() const
0106 {
0107     return !d->mUniqueId.isEmpty(); // This should not use the uniqueId getter due to the fallback!
0108 }
0109 
0110 QString Entry::name() const
0111 {
0112     return d->mName;
0113 }
0114 
0115 void Entry::setName(const QString &name)
0116 {
0117     d->mName = name;
0118 }
0119 
0120 QString Entry::uniqueId() const
0121 {
0122     return d->mUniqueId.isEmpty() ? d->mRequestedUniqueId : d->mUniqueId;
0123 }
0124 
0125 void Entry::setUniqueId(const QString &id)
0126 {
0127     d->mUniqueId = id;
0128 }
0129 
0130 QString Entry::providerId() const
0131 {
0132     return d->mProviderId;
0133 }
0134 
0135 void Entry::setProviderId(const QString &id)
0136 {
0137     d->mProviderId = id;
0138 }
0139 
0140 QStringList KNSCore::Entry::tags() const
0141 {
0142     return d->mTags;
0143 }
0144 
0145 void KNSCore::Entry::setTags(const QStringList &tags)
0146 {
0147     d->mTags = tags;
0148 }
0149 
0150 QString Entry::category() const
0151 {
0152     return d->mCategory;
0153 }
0154 
0155 void Entry::setCategory(const QString &category)
0156 {
0157     d->mCategory = category;
0158 }
0159 
0160 QUrl Entry::homepage() const
0161 {
0162     return d->mHomepage;
0163 }
0164 
0165 void Entry::setHomepage(const QUrl &page)
0166 {
0167     d->mHomepage = page;
0168 }
0169 
0170 Author Entry::author() const
0171 {
0172     return d->mAuthor;
0173 }
0174 
0175 void Entry::setAuthor(const KNSCore::Author &author)
0176 {
0177     d->mAuthor = author;
0178 }
0179 
0180 QString Entry::license() const
0181 {
0182     return d->mLicense;
0183 }
0184 
0185 void Entry::setLicense(const QString &license)
0186 {
0187     d->mLicense = license;
0188 }
0189 
0190 QString Entry::summary() const
0191 {
0192     return d->mSummary;
0193 }
0194 
0195 void Entry::setSummary(const QString &summary)
0196 {
0197     d->mSummary = summary;
0198 }
0199 
0200 QString Entry::shortSummary() const
0201 {
0202     return d->mShortSummary;
0203 }
0204 
0205 void Entry::setShortSummary(const QString &summary)
0206 {
0207     d->mShortSummary = summary;
0208 }
0209 
0210 void Entry::setChangelog(const QString &changelog)
0211 {
0212     d->mChangelog = changelog;
0213 }
0214 
0215 QString Entry::changelog() const
0216 {
0217     return d->mChangelog;
0218 }
0219 
0220 QString Entry::version() const
0221 {
0222     return d->mVersion;
0223 }
0224 
0225 void Entry::setVersion(const QString &version)
0226 {
0227     d->mVersion = version;
0228 }
0229 
0230 QDate Entry::releaseDate() const
0231 {
0232     return d->mReleaseDate;
0233 }
0234 
0235 void Entry::setReleaseDate(const QDate &releasedate)
0236 {
0237     d->mReleaseDate = releasedate;
0238 }
0239 
0240 QString Entry::payload() const
0241 {
0242     return d->mPayload;
0243 }
0244 
0245 void Entry::setPayload(const QString &url)
0246 {
0247     d->mPayload = url;
0248 }
0249 
0250 QDate Entry::updateReleaseDate() const
0251 {
0252     return d->mUpdateReleaseDate;
0253 }
0254 
0255 void Entry::setUpdateReleaseDate(const QDate &releasedate)
0256 {
0257     d->mUpdateReleaseDate = releasedate;
0258 }
0259 
0260 QString Entry::updateVersion() const
0261 {
0262     return d->mUpdateVersion;
0263 }
0264 
0265 void Entry::setUpdateVersion(const QString &version)
0266 {
0267     d->mUpdateVersion = version;
0268 }
0269 
0270 QString Entry::previewUrl(PreviewType type) const
0271 {
0272     return d->mPreviewUrl[type];
0273 }
0274 
0275 void Entry::setPreviewUrl(const QString &url, PreviewType type)
0276 {
0277     d->mPreviewUrl[type] = url;
0278 }
0279 
0280 QImage Entry::previewImage(PreviewType type) const
0281 {
0282     return d->mPreviewImage[type];
0283 }
0284 
0285 void Entry::setPreviewImage(const QImage &image, PreviewType type)
0286 {
0287     d->mPreviewImage[type] = image;
0288 }
0289 
0290 int Entry::rating() const
0291 {
0292     return d->mRating;
0293 }
0294 
0295 void Entry::setRating(int rating)
0296 {
0297     d->mRating = rating;
0298 }
0299 
0300 int Entry::numberOfComments() const
0301 {
0302     return d->mNumberOfComments;
0303 }
0304 
0305 void Entry::setNumberOfComments(int comments)
0306 {
0307     d->mNumberOfComments = comments;
0308 }
0309 
0310 int Entry::downloadCount() const
0311 {
0312     return d->mDownloadCount;
0313 }
0314 
0315 void Entry::setDownloadCount(int downloads)
0316 {
0317     d->mDownloadCount = downloads;
0318 }
0319 
0320 int Entry::numberFans() const
0321 {
0322     return d->mNumberFans;
0323 }
0324 
0325 void Entry::setNumberFans(int fans)
0326 {
0327     d->mNumberFans = fans;
0328 }
0329 
0330 QString Entry::donationLink() const
0331 {
0332     return d->mDonationLink;
0333 }
0334 
0335 void Entry::setDonationLink(const QString &link)
0336 {
0337     d->mDonationLink = link;
0338 }
0339 
0340 int Entry::numberKnowledgebaseEntries() const
0341 {
0342     return d->mNumberKnowledgebaseEntries;
0343 }
0344 void Entry::setNumberKnowledgebaseEntries(int num)
0345 {
0346     d->mNumberKnowledgebaseEntries = num;
0347 }
0348 
0349 QString Entry::knowledgebaseLink() const
0350 {
0351     return d->mKnowledgebaseLink;
0352 }
0353 void Entry::setKnowledgebaseLink(const QString &link)
0354 {
0355     d->mKnowledgebaseLink = link;
0356 }
0357 
0358 Entry::Source Entry::source() const
0359 {
0360     return d->mSource;
0361 }
0362 
0363 void Entry::setEntryType(Entry::EntryType type)
0364 {
0365     d->mEntryType = type;
0366 }
0367 
0368 Entry::EntryType Entry::entryType() const
0369 {
0370     return d->mEntryType;
0371 }
0372 
0373 void Entry::setSource(Source source)
0374 {
0375     d->mSource = source;
0376 }
0377 
0378 KNSCore::Entry::Status Entry::status() const
0379 {
0380     return d->mStatus;
0381 }
0382 
0383 void Entry::setStatus(KNSCore::Entry::Status status)
0384 {
0385     d->mStatus = status;
0386 }
0387 
0388 void KNSCore::Entry::setInstalledFiles(const QStringList &files)
0389 {
0390     d->mInstalledFiles = files;
0391 }
0392 
0393 QStringList KNSCore::Entry::installedFiles() const
0394 {
0395     return d->mInstalledFiles;
0396 }
0397 
0398 QStringList KNSCore::Entry::uninstalledFiles() const
0399 {
0400     return d->mUnInstalledFiles;
0401 }
0402 
0403 int KNSCore::Entry::downloadLinkCount() const
0404 {
0405     return d->mDownloadLinkInformationList.size();
0406 }
0407 
0408 QList<KNSCore::Entry::DownloadLinkInformation> KNSCore::Entry::downloadLinkInformationList() const
0409 {
0410     return d->mDownloadLinkInformationList;
0411 }
0412 
0413 void KNSCore::Entry::appendDownloadLinkInformation(const KNSCore::Entry::DownloadLinkInformation &info)
0414 {
0415     d->mDownloadLinkInformationList.append(info);
0416 }
0417 
0418 void Entry::clearDownloadLinkInformation()
0419 {
0420     d->mDownloadLinkInformationList.clear();
0421 }
0422 
0423 static QXmlStreamReader::TokenType readNextSkipComments(QXmlStreamReader *xml)
0424 {
0425     do {
0426         xml->readNext();
0427     } while (xml->tokenType() == QXmlStreamReader::Comment || (xml->tokenType() == QXmlStreamReader::Characters && xml->text().trimmed().isEmpty()));
0428     return xml->tokenType();
0429 }
0430 
0431 static QString readText(QXmlStreamReader *xml)
0432 {
0433     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
0434     QString ret;
0435     const auto token = readNextSkipComments(xml);
0436     if (token == QXmlStreamReader::Characters) {
0437         ret = xml->text().toString();
0438     }
0439     return ret;
0440 }
0441 
0442 static QString readStringTrimmed(QXmlStreamReader *xml)
0443 {
0444     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
0445     QString ret = readText(xml).trimmed();
0446 
0447     if (xml->tokenType() == QXmlStreamReader::Characters) {
0448         readNextSkipComments(xml);
0449     }
0450     Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
0451     return ret;
0452 }
0453 
0454 static int readInt(QXmlStreamReader *xml)
0455 {
0456     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
0457     int ret = readText(xml).toInt();
0458 
0459     xml->readNext();
0460     Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
0461     return ret;
0462 }
0463 
0464 bool KNSCore::Entry::setEntryXML(QXmlStreamReader &reader)
0465 {
0466     if (reader.name() != QLatin1String("stuff")) {
0467         qCWarning(KNEWSTUFFCORE) << "Parsing Entry from invalid XML. Reader tag name was expected to be \"stuff\", but was found as:" << reader.name();
0468         return false;
0469     }
0470 
0471     d->mCategory = reader.attributes().value(QStringLiteral("category")).toString();
0472 
0473     while (!reader.atEnd()) {
0474         const auto token = readNextSkipComments(&reader);
0475         if (token == QXmlStreamReader::EndElement) {
0476             break;
0477         } else if (token != QXmlStreamReader::StartElement) {
0478             continue;
0479         }
0480 
0481         if (reader.name() == QLatin1String("name")) {
0482             // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
0483             d->mName = reader.readElementText(QXmlStreamReader::SkipChildElements);
0484         } else if (reader.name() == QLatin1String("author")) {
0485             // ### careful, the following variables are string views that become invalid when we
0486             // proceed with reading from reader, such as the readStringTrimmed call below does!
0487             const auto email = reader.attributes().value(QStringLiteral("email"));
0488             const auto jabber = reader.attributes().value(QStringLiteral("im"));
0489             const auto homepage = reader.attributes().value(QStringLiteral("homepage"));
0490             d->mAuthor.setEmail(email.toString());
0491             d->mAuthor.setJabber(jabber.toString());
0492             d->mAuthor.setHomepage(homepage.toString());
0493             d->mAuthor.setName(readStringTrimmed(&reader));
0494         } else if (reader.name() == QLatin1String("providerid")) {
0495             d->mProviderId = reader.readElementText(QXmlStreamReader::SkipChildElements);
0496         } else if (reader.name() == QLatin1String("homepage")) {
0497             d->mHomepage = QUrl(reader.readElementText(QXmlStreamReader::SkipChildElements));
0498         } else if (reader.name() == QLatin1String("licence")) { // krazy:exclude=spelling
0499             d->mLicense = readStringTrimmed(&reader);
0500         } else if (reader.name() == QLatin1String("summary")) {
0501             d->mSummary = reader.readElementText(QXmlStreamReader::SkipChildElements);
0502         } else if (reader.name() == QLatin1String("changelog")) {
0503             d->mChangelog = reader.readElementText(QXmlStreamReader::SkipChildElements);
0504         } else if (reader.name() == QLatin1String("version")) {
0505             d->mVersion = readStringTrimmed(&reader);
0506         } else if (reader.name() == QLatin1String("releasedate")) {
0507             d->mReleaseDate = QDate::fromString(readStringTrimmed(&reader), Qt::ISODate);
0508         } else if (reader.name() == QLatin1String("preview")) {
0509             // TODO support for all 6 image links
0510             d->mPreviewUrl[PreviewSmall1] = readStringTrimmed(&reader);
0511         } else if (reader.name() == QLatin1String("previewBig")) {
0512             d->mPreviewUrl[PreviewBig1] = readStringTrimmed(&reader);
0513         } else if (reader.name() == QLatin1String("payload")) {
0514             d->mPayload = readStringTrimmed(&reader);
0515         } else if (reader.name() == QLatin1String("rating")) {
0516             d->mRating = readInt(&reader);
0517         } else if (reader.name() == QLatin1String("downloads")) {
0518             d->mDownloadCount = readInt(&reader);
0519         } else if (reader.name() == QLatin1String("category")) {
0520             d->mCategory = reader.readElementText(QXmlStreamReader::SkipChildElements);
0521         } else if (reader.name() == QLatin1String("signature")) {
0522             d->mSignature = reader.readElementText(QXmlStreamReader::SkipChildElements);
0523         } else if (reader.name() == QLatin1String("checksum")) {
0524             d->mChecksum = reader.readElementText(QXmlStreamReader::SkipChildElements);
0525         } else if (reader.name() == QLatin1String("installedfile")) {
0526             d->mInstalledFiles.append(reader.readElementText(QXmlStreamReader::SkipChildElements));
0527         } else if (reader.name() == QLatin1String("id")) {
0528             d->mUniqueId = reader.readElementText(QXmlStreamReader::SkipChildElements);
0529         } else if (reader.name() == QLatin1String("tags")) {
0530             d->mTags = reader.readElementText(QXmlStreamReader::SkipChildElements).split(QLatin1Char(','));
0531         } else if (reader.name() == QLatin1String("status")) {
0532             const auto statusText = readText(&reader);
0533             if (statusText == QLatin1String("installed")) {
0534                 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
0535                 d->mStatus = KNSCore::Entry::Installed;
0536             } else if (statusText == QLatin1String("updateable")) {
0537                 d->mStatus = KNSCore::Entry::Updateable;
0538             }
0539             if (reader.tokenType() == QXmlStreamReader::Characters) {
0540                 readNextSkipComments(&reader);
0541             }
0542         }
0543         Q_ASSERT_X(reader.tokenType() == QXmlStreamReader::EndElement,
0544                    Q_FUNC_INFO,
0545                    QStringLiteral("token name was %1 and the type was %2").arg(reader.name().toString(), reader.tokenString()).toLocal8Bit().data());
0546     }
0547 
0548     // Validation
0549     if (d->mName.isEmpty()) {
0550         qWarning() << "Entry: no name given";
0551         return false;
0552     }
0553 
0554     if (d->mUniqueId.isEmpty()) {
0555         if (!d->mPayload.isEmpty()) {
0556             d->mUniqueId = d->mPayload;
0557         } else {
0558             d->mUniqueId = d->mName;
0559         }
0560     }
0561 
0562     if (d->mPayload.isEmpty()) {
0563         qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
0564         return false;
0565     }
0566     return true;
0567 }
0568 
0569 bool KNSCore::Entry::setEntryXML(const QDomElement &xmldata)
0570 {
0571     if (xmldata.tagName() != QLatin1String("stuff")) {
0572         qWarning() << "Parsing Entry from invalid XML";
0573         return false;
0574     }
0575 
0576     d->mCategory = xmldata.attribute(QStringLiteral("category"));
0577 
0578     QDomNode n;
0579     for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) {
0580         QDomElement e = n.toElement();
0581         if (e.tagName() == QLatin1String("name")) {
0582             // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
0583             d->mName = e.text().trimmed();
0584         } else if (e.tagName() == QLatin1String("author")) {
0585             QString email = e.attribute(QStringLiteral("email"));
0586             QString jabber = e.attribute(QStringLiteral("im"));
0587             QString homepage = e.attribute(QStringLiteral("homepage"));
0588             d->mAuthor.setName(e.text().trimmed());
0589             d->mAuthor.setEmail(email);
0590             d->mAuthor.setJabber(jabber);
0591             d->mAuthor.setHomepage(homepage);
0592         } else if (e.tagName() == QLatin1String("providerid")) {
0593             d->mProviderId = e.text();
0594         } else if (e.tagName() == QLatin1String("homepage")) {
0595             d->mHomepage = QUrl(e.text());
0596         } else if (e.tagName() == QLatin1String("licence")) { // krazy:exclude=spelling
0597             d->mLicense = e.text().trimmed();
0598         } else if (e.tagName() == QLatin1String("summary")) {
0599             d->mSummary = e.text();
0600         } else if (e.tagName() == QLatin1String("changelog")) {
0601             d->mChangelog = e.text();
0602         } else if (e.tagName() == QLatin1String("version")) {
0603             d->mVersion = e.text().trimmed();
0604         } else if (e.tagName() == QLatin1String("releasedate")) {
0605             d->mReleaseDate = QDate::fromString(e.text().trimmed(), Qt::ISODate);
0606         } else if (e.tagName() == QLatin1String("preview")) {
0607             // TODO support for all 6 image links
0608             d->mPreviewUrl[PreviewSmall1] = e.text().trimmed();
0609         } else if (e.tagName() == QLatin1String("previewBig")) {
0610             d->mPreviewUrl[PreviewBig1] = e.text().trimmed();
0611         } else if (e.tagName() == QLatin1String("payload")) {
0612             d->mPayload = e.text().trimmed();
0613         } else if (e.tagName() == QLatin1String("rating")) {
0614             d->mRating = e.text().toInt();
0615         } else if (e.tagName() == QLatin1String("downloads")) {
0616             d->mDownloadCount = e.text().toInt();
0617         } else if (e.tagName() == QLatin1String("category")) {
0618             d->mCategory = e.text();
0619         } else if (e.tagName() == QLatin1String("signature")) {
0620             d->mSignature = e.text();
0621         } else if (e.tagName() == QLatin1String("checksum")) {
0622             d->mChecksum = e.text();
0623         } else if (e.tagName() == QLatin1String("installedfile")) {
0624             // TODO KF6 Add a "installeddirectory" entry to avoid
0625             // all the issues with the "/*" notation which is currently used as a workaround
0626             d->mInstalledFiles.append(e.text());
0627         } else if (e.tagName() == QLatin1String("id")) {
0628             d->mUniqueId = e.text();
0629         } else if (e.tagName() == QLatin1String("tags")) {
0630             d->mTags = e.text().split(QLatin1Char(','));
0631         } else if (e.tagName() == QLatin1String("status")) {
0632             QString statusText = e.text();
0633             if (statusText == QLatin1String("installed")) {
0634                 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
0635                 d->mStatus = KNSCore::Entry::Installed;
0636             } else if (statusText == QLatin1String("updateable")) {
0637                 d->mStatus = KNSCore::Entry::Updateable;
0638             }
0639         }
0640     }
0641 
0642     // Validation
0643     if (d->mName.isEmpty()) {
0644         qWarning() << "Entry: no name given";
0645         return false;
0646     }
0647 
0648     if (d->mUniqueId.isEmpty()) {
0649         if (!d->mPayload.isEmpty()) {
0650             d->mUniqueId = d->mPayload;
0651         } else {
0652             d->mUniqueId = d->mName;
0653         }
0654     }
0655 
0656     if (d->mPayload.isEmpty()) {
0657         qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
0658         return false;
0659     }
0660     return true;
0661 }
0662 
0663 /**
0664  * get the xml string for the entry
0665  */
0666 QDomElement KNSCore::Entry::entryXML() const
0667 {
0668     Q_ASSERT(!d->mUniqueId.isEmpty());
0669     Q_ASSERT(!d->mProviderId.isEmpty());
0670 
0671     QDomDocument doc;
0672 
0673     QDomElement el = doc.createElement(QStringLiteral("stuff"));
0674     el.setAttribute(QStringLiteral("category"), d->mCategory);
0675 
0676     QString name = d->mName;
0677 
0678     QDomElement e;
0679     e = addElement(doc, el, QStringLiteral("name"), name);
0680     // todo: add language attribute
0681     (void)addElement(doc, el, QStringLiteral("providerid"), d->mProviderId);
0682 
0683     QDomElement author = addElement(doc, el, QStringLiteral("author"), d->mAuthor.name());
0684     if (!d->mAuthor.email().isEmpty()) {
0685         author.setAttribute(QStringLiteral("email"), d->mAuthor.email());
0686     }
0687     if (!d->mAuthor.homepage().isEmpty()) {
0688         author.setAttribute(QStringLiteral("homepage"), d->mAuthor.homepage());
0689     }
0690     if (!d->mAuthor.jabber().isEmpty()) {
0691         author.setAttribute(QStringLiteral("im"), d->mAuthor.jabber());
0692     }
0693     // FIXME: 'jabber' or 'im'? consult with kopete guys...
0694     addElement(doc, el, QStringLiteral("homepage"), d->mHomepage.url());
0695     (void)addElement(doc, el, QStringLiteral("licence"), d->mLicense); // krazy:exclude=spelling
0696     (void)addElement(doc, el, QStringLiteral("version"), d->mVersion);
0697     if ((d->mRating > 0) || (d->mDownloadCount > 0)) {
0698         (void)addElement(doc, el, QStringLiteral("rating"), QString::number(d->mRating));
0699         (void)addElement(doc, el, QStringLiteral("downloads"), QString::number(d->mDownloadCount));
0700     }
0701     if (!d->mSignature.isEmpty()) {
0702         (void)addElement(doc, el, QStringLiteral("signature"), d->mSignature);
0703     }
0704     if (!d->mChecksum.isEmpty()) {
0705         (void)addElement(doc, el, QStringLiteral("checksum"), d->mChecksum);
0706     }
0707     for (const QString &file : std::as_const(d->mInstalledFiles)) {
0708         (void)addElement(doc, el, QStringLiteral("installedfile"), file);
0709     }
0710     if (!d->mUniqueId.isEmpty()) {
0711         addElement(doc, el, QStringLiteral("id"), d->mUniqueId);
0712     }
0713 
0714     (void)addElement(doc, el, QStringLiteral("releasedate"), d->mReleaseDate.toString(Qt::ISODate));
0715 
0716     e = addElement(doc, el, QStringLiteral("summary"), d->mSummary);
0717     e = addElement(doc, el, QStringLiteral("changelog"), d->mChangelog);
0718     e = addElement(doc, el, QStringLiteral("preview"), d->mPreviewUrl[PreviewSmall1]);
0719     e = addElement(doc, el, QStringLiteral("previewBig"), d->mPreviewUrl[PreviewBig1]);
0720     e = addElement(doc, el, QStringLiteral("payload"), d->mPayload);
0721     e = addElement(doc, el, QStringLiteral("tags"), d->mTags.join(QLatin1Char(',')));
0722 
0723     if (d->mStatus == KNSCore::Entry::Installed) {
0724         (void)addElement(doc, el, QStringLiteral("status"), QStringLiteral("installed"));
0725     }
0726     if (d->mStatus == KNSCore::Entry::Updateable) {
0727         (void)addElement(doc, el, QStringLiteral("status"), QStringLiteral("updateable"));
0728     }
0729 
0730     return el;
0731 }
0732 
0733 void KNSCore::Entry::setEntryDeleted()
0734 {
0735     setStatus(Entry::Deleted);
0736     d->mUnInstalledFiles = installedFiles();
0737     setInstalledFiles(QStringList());
0738 }
0739 
0740 void KNSCore::Entry::setEntryRequestedId(const QString &id)
0741 {
0742     d->mRequestedUniqueId = id;
0743 }
0744 
0745 QString KNSCore::replaceBBCode(const QString &unformattedText)
0746 {
0747     QString text(unformattedText);
0748     text.replace(QLatin1String("[b]"), QLatin1String("<b>"));
0749     text.replace(QLatin1String("[/b]"), QLatin1String("</b>"));
0750     text.replace(QLatin1String("[i]"), QLatin1String("<i>"));
0751     text.replace(QLatin1String("[/i]"), QLatin1String("</i>"));
0752     text.replace(QLatin1String("[u]"), QLatin1String("<i>"));
0753     text.replace(QLatin1String("[/u]"), QLatin1String("</i>"));
0754     text.replace(QLatin1String("\\\""), QLatin1String("\""));
0755     text.replace(QLatin1String("\\\'"), QLatin1String("\'"));
0756     text.replace(QLatin1String("[li]"), QLatin1String("* ")); // TODO: better replacement for list elements?
0757     text.remove(QStringLiteral("[/li]"));
0758     text.remove(QStringLiteral("[url]"));
0759     text.remove(QStringLiteral("[/url]"));
0760     return text;
0761 }
0762 
0763 QDebug KNSCore::operator<<(QDebug debug, const KNSCore::Entry &entry)
0764 {
0765     QDebugStateSaver saver(debug);
0766 
0767     const static QMetaEnum metaEnum = QMetaEnum::fromType<KNSCore::Entry::Status>();
0768     bool deleted = entry.status() == Entry::Status::Deleted;
0769 
0770     debug.nospace() << "KNSCore::Entry(uniqueId: " << entry.uniqueId() << ", name:" << entry.name() << ", status: " << metaEnum.valueToKey(entry.status())
0771                     << ", " << (deleted ? "uninstalled" : "installed") << "Files: " // When the entry is installed, it can not have uninstalledFiles
0772                     << (deleted ? entry.uninstalledFiles() : entry.installedFiles()) << ')';
0773     return debug;
0774 }
0775 
0776 #include "moc_entry.cpp"