File indexing completed on 2025-01-19 03:58:02

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-05-15
0007  * Description : a node container for bookmarks
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "bookmarknode.h"
0016 
0017 // Qt includes
0018 
0019 #include <QPointer>
0020 #include <QFile>
0021 
0022 // KDE includes
0023 
0024 #include <klocalizedstring.h>
0025 
0026 // Local includes
0027 
0028 #include "digikam_debug.h"
0029 
0030 namespace Digikam
0031 {
0032 
0033 class Q_DECL_HIDDEN BookmarkNode::Private
0034 {
0035 public:
0036 
0037     explicit Private()
0038       : parent(nullptr),
0039         type  (BookmarkNode::Root)
0040     {
0041     }
0042 
0043     BookmarkNode*        parent;
0044     Type                 type;
0045     QList<BookmarkNode*> children;
0046 };
0047 
0048 BookmarkNode::BookmarkNode(BookmarkNode::Type type, BookmarkNode* const parent)
0049     : QObject(nullptr),
0050       d      (new Private)
0051 {
0052     expanded  = false;
0053     d->parent = parent;
0054     d->type   = type;
0055 
0056     if (parent)
0057     {
0058         parent->add(this);
0059     }
0060 }
0061 
0062 BookmarkNode::~BookmarkNode()
0063 {
0064     if (d->parent)
0065     {
0066         d->parent->remove(this);
0067     }
0068 
0069     while (d->children.size())
0070     {
0071         delete d->children.takeFirst();
0072     }
0073 
0074     delete d;
0075 }
0076 
0077 bool BookmarkNode::operator==(const BookmarkNode& other) const
0078 {
0079     if ((url                 != other.url)           ||
0080         (title               != other.title)         ||
0081         (desc                != other.desc)          ||
0082         (expanded            != other.expanded)      ||
0083         (dateAdded           != other.dateAdded)     ||
0084         (d->type             != other.d->type)       ||
0085         (d->children.count() != other.d->children.count()))
0086     {
0087         return false;
0088     }
0089 
0090     for (int i = 0 ; i < d->children.count() ; ++i)
0091     {
0092         if (!((*(d->children[i])) == (*(other.d->children[i]))))
0093         {
0094             return false;
0095         }
0096     }
0097 
0098     return true;
0099 }
0100 
0101 BookmarkNode::Type BookmarkNode::type() const
0102 {
0103     return d->type;
0104 }
0105 
0106 void BookmarkNode::setType(Type type)
0107 {
0108     d->type = type;
0109 }
0110 
0111 QList<BookmarkNode*> BookmarkNode::children() const
0112 {
0113     return d->children;
0114 }
0115 
0116 BookmarkNode* BookmarkNode::parent() const
0117 {
0118     return d->parent;
0119 }
0120 
0121 void BookmarkNode::add(BookmarkNode* const child, int offset)
0122 {
0123     Q_ASSERT(child->d->type != Root);
0124 
0125     if (child->d->parent)
0126     {
0127         child->d->parent->remove(child);
0128     }
0129 
0130     child->d->parent = this;
0131 
0132     if (offset == -1)
0133     {
0134         offset = d->children.size();
0135     }
0136 
0137     d->children.insert(offset, child);
0138 }
0139 
0140 void BookmarkNode::remove(BookmarkNode* const child)
0141 {
0142     child->d->parent = nullptr;
0143     d->children.removeAll(child);
0144 }
0145 
0146 // -------------------------------------------------------
0147 
0148 XbelReader::XbelReader()
0149 {
0150 }
0151 
0152 BookmarkNode* XbelReader::read(const QString& fileName)
0153 {
0154     QFile file(fileName);
0155 
0156     if (!file.exists() || !file.open(QFile::ReadOnly))
0157     {
0158         BookmarkNode* const root   = new BookmarkNode(BookmarkNode::Root);
0159         BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root);
0160         folder->title              = i18n("Bookmark folder");
0161 
0162         return root;
0163     }
0164 
0165     return read(&file, true);
0166 }
0167 
0168 BookmarkNode* XbelReader::read(QIODevice* const device, bool addRootFolder)
0169 {
0170     BookmarkNode* const root = new BookmarkNode(BookmarkNode::Root);
0171     setDevice(device);
0172 
0173     if (readNextStartElement())
0174     {
0175         QString version = attributes().value(QLatin1String("version")).toString();
0176 
0177         if ((name() == QLatin1String("xbel")) &&
0178             (version.isEmpty() || (version == QLatin1String("1.0"))))
0179         {
0180             if (addRootFolder)
0181             {
0182                 BookmarkNode* const folder = new BookmarkNode(BookmarkNode::RootFolder, root);
0183                 folder->title              = i18n("Bookmark folder");
0184                 readXBEL(folder);
0185             }
0186             else
0187             {
0188                 readXBEL(root);
0189             }
0190         }
0191         else
0192         {
0193             raiseError(i18n("The file is not an XBEL version 1.0 file."));
0194         }
0195     }
0196 
0197     return root;
0198 }
0199 
0200 void XbelReader::readXBEL(BookmarkNode* const parent)
0201 {
0202     Q_ASSERT(isStartElement() && (name() == QLatin1String("xbel")));
0203 
0204     while (readNextStartElement())
0205     {
0206         if      (name() == QLatin1String("folder"))
0207         {
0208             readFolder(parent);
0209         }
0210         else if (name() == QLatin1String("bookmark"))
0211         {
0212             readBookmarkNode(parent);
0213         }
0214         else if (name() == QLatin1String("separator"))
0215         {
0216             readSeparator(parent);
0217         }
0218         else
0219         {
0220             skipCurrentElement();
0221         }
0222     }
0223 }
0224 
0225 void XbelReader::readFolder(BookmarkNode* const parent)
0226 {
0227     Q_ASSERT(isStartElement() && (name() == QLatin1String("folder")));
0228 
0229     QPointer<BookmarkNode> folder = new BookmarkNode(BookmarkNode::Folder, parent);
0230     folder->expanded              = (attributes().value(QLatin1String("folded")) == QLatin1String("no"));
0231 
0232     while (readNextStartElement())
0233     {
0234         if      (name() == QLatin1String("title"))
0235         {
0236             readTitle(folder);
0237         }
0238         else if (name() == QLatin1String("desc"))
0239         {
0240             readDescription(folder);
0241         }
0242         else if (name() == QLatin1String("folder"))
0243         {
0244             readFolder(folder);
0245         }
0246         else if (name() == QLatin1String("bookmark"))
0247         {
0248             readBookmarkNode(folder);
0249         }
0250         else if (name() == QLatin1String("separator"))
0251         {
0252             readSeparator(folder);
0253         }
0254         else
0255         {
0256             skipCurrentElement();
0257         }
0258     }
0259 }
0260 
0261 void XbelReader::readTitle(BookmarkNode* const parent)
0262 {
0263     Q_ASSERT(isStartElement() && (name() == QLatin1String("title")));
0264 
0265     parent->title = readElementText();
0266 }
0267 
0268 void XbelReader::readDescription(BookmarkNode* const parent)
0269 {
0270     Q_ASSERT(isStartElement() && (name() == QLatin1String("desc")));
0271 
0272     parent->desc = readElementText();
0273 }
0274 
0275 void XbelReader::readSeparator(BookmarkNode* const parent)
0276 {
0277     new BookmarkNode(BookmarkNode::Separator, parent);
0278 
0279     // empty elements have a start and end element
0280 
0281     readNext();
0282 }
0283 
0284 void XbelReader::readBookmarkNode(BookmarkNode* const parent)
0285 {
0286     Q_ASSERT(isStartElement() && (name() == QLatin1String("bookmark")));
0287 
0288     BookmarkNode* const bookmark = new BookmarkNode(BookmarkNode::Bookmark, parent);
0289     bookmark->url                = attributes().value(QLatin1String("href")).toString();
0290     QString date                 = attributes().value(QLatin1String("added")).toString();
0291     bookmark->dateAdded          = QDateTime::fromString(date, Qt::ISODate);
0292 
0293     while (readNextStartElement())
0294     {
0295         if      (name() == QLatin1String("title"))
0296         {
0297             readTitle(bookmark);
0298         }
0299         else if (name() == QLatin1String("desc"))
0300         {
0301             readDescription(bookmark);
0302         }
0303         else
0304         {
0305             skipCurrentElement();
0306         }
0307     }
0308 
0309     if (bookmark->title.isEmpty())
0310     {
0311         bookmark->title = i18n("Unknown title");
0312     }
0313 }
0314 
0315 // -------------------------------------------------------
0316 
0317 XbelWriter::XbelWriter()
0318 {
0319     setAutoFormatting(true);
0320 }
0321 
0322 bool XbelWriter::write(const QString& fileName, const BookmarkNode* const root)
0323 {
0324     QFile file(fileName);
0325 
0326     if (!root || !file.open(QFile::WriteOnly))
0327     {
0328         return false;
0329     }
0330 
0331     return write(&file, root);
0332 }
0333 
0334 bool XbelWriter::write(QIODevice* const device, const BookmarkNode* const root)
0335 {
0336     setDevice(device);
0337 
0338     writeStartDocument();
0339     writeDTD(QLatin1String("<!DOCTYPE xbel>"));
0340     writeStartElement(QLatin1String("xbel"));
0341     writeAttribute(QLatin1String("version"), QLatin1String("1.0"));
0342 
0343     if (root->type() == BookmarkNode::Root)
0344     {
0345         BookmarkNode* const rootFolder = root->children().constFirst();
0346 
0347         for (int i = 0  ; i < rootFolder->children().count() ; ++i)
0348         {
0349             writeItem(rootFolder->children().at(i));
0350         }
0351     }
0352     else
0353     {
0354         writeItem(root);
0355     }
0356 
0357     writeEndDocument();
0358 
0359     return true;
0360 }
0361 
0362 void XbelWriter::writeItem(const BookmarkNode* const parent)
0363 {
0364     switch (parent->type())
0365     {
0366         case BookmarkNode::Folder:
0367             writeStartElement(QLatin1String("folder"));
0368             writeAttribute(QLatin1String("folded"), parent->expanded ? QLatin1String("no") : QLatin1String("yes"));
0369             writeTextElement(QLatin1String("title"), parent->title);
0370 
0371             for (int i = 0 ; i < parent->children().count() ; ++i)
0372             {
0373                 writeItem(parent->children().at(i));
0374             }
0375 
0376             writeEndElement();
0377             break;
0378 
0379         case BookmarkNode::Bookmark:
0380             writeStartElement(QLatin1String("bookmark"));
0381 
0382             if (!parent->url.isEmpty())
0383             {
0384                 writeAttribute(QLatin1String("href"), parent->url);
0385             }
0386 
0387             if (parent->dateAdded.isValid())
0388             {
0389                 writeAttribute(QLatin1String("added"), parent->dateAdded.toString(Qt::ISODate));
0390             }
0391 
0392             if (!parent->desc.isEmpty())
0393             {
0394                 writeAttribute(QLatin1String("desc"), parent->desc);
0395             }
0396 
0397             writeTextElement(QLatin1String("title"), parent->title);
0398 
0399             writeEndElement();
0400             break;
0401 
0402         case BookmarkNode::Separator:
0403             writeEmptyElement(QLatin1String("separator"));
0404             break;
0405 
0406         default:
0407             break;
0408     }
0409 }
0410 
0411 } // namespace Digikam
0412 
0413 #include "moc_bookmarknode.cpp"