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