/* ============================================================ * rekonq * ============================================================ * SPDX-License-Identifier: GPL-3.0-only * Copyright (C) 2022 aqua * ============================================================ * Description: rekonq bookmarks model * ============================================================ */ #include "bookmarkstreeformat_xbel.h" #include "bookmarkstreeitem.hpp" #include #include inline void readNodeAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attributes) { // id if (attributes.hasAttribute(xbel::attr_id)) item->setData(BookmarksTreeItem::Id, attributes.value(xbel::attr_id).toString()); // added if (attributes.hasAttribute(xbel::attr_added)) { const auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_added), Qt::ISODate); if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Added, dateTime); } } inline void readUrlAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attributes) { // href (required) if (attributes.hasAttribute(xbel::attr_href)) item->setData(BookmarksTreeItem::Href, attributes.value(xbel::attr_href).toString()); // visited if (attributes.hasAttribute(xbel::attr_visited)) { const auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_visited), Qt::ISODate); if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Visited, dateTime); } // modified if (attributes.hasAttribute(xbel::attr_modified)) { const auto dateTime = QDateTime::fromString(attributes.value(xbel::attr_modified), Qt::ISODate); if (!dateTime.isNull() && dateTime.isValid()) item->setData(BookmarksTreeItem::Modified, dateTime); } } [[nodiscard]] inline QList readItemAttributes(BookmarksTreeItem *item, const QXmlStreamAttributes &attr) { QList errors; switch (item->type()) { case BookmarksTreeItem::Root: if (!attr.hasAttribute(xbel::attr_version)) { errors.append({QObject::tr("%1 xbel has no version").arg(__PRETTY_FUNCTION__)}); } else if (attr.value(xbel::attr_version).compare(QLatin1String{xbel::attr_version_value}) != 0) { errors.append({QObject::tr("%1 unknown xbel version").arg(__PRETTY_FUNCTION__)}); } readNodeAttributes(item, attr); break; case BookmarksTreeItem::Folder: readNodeAttributes(item, attr); if (attr.hasAttribute(xbel::attr_folded)) item->setExpanded(attr.value(xbel::attr_folded).compare(QLatin1String{xbel::attr_folded_yes}) != 0); break; case BookmarksTreeItem::Separator: break; case BookmarksTreeItem::Bookmark: readNodeAttributes(item, attr); readUrlAttributes(item, attr); break; case BookmarksTreeItem::Alias: if (!attr.hasAttribute(xbel::attr_ref)) { errors.append({QObject::tr("%1 Alias has no ref").arg(__PRETTY_FUNCTION__)}); } else item->setData(BookmarksTreeItem::Id, attr.value(xbel::attr_ref).toString()); break; } return errors; } /** * Recursively parse XBEL * @param reader * @param parent * @return true if there were errors during parsing */ [[nodiscard]] inline QList readChildElements(QXmlStreamReader &reader, BookmarksTreeItem *parent) { QList errors; while (reader.readNextStartElement()) { if (reader.hasError()) { errors.append({QObject::tr("%1 error during parsing: %2").arg(__PRETTY_FUNCTION__, reader.errorString())}); } const auto name = reader.name(); if (name == xbel::elem_title) { parent->setData(BookmarksTreeItem::Title, reader.readElementText()); } else if (name == xbel::elem_desc) { parent->setData(BookmarksTreeItem::Description, reader.readElementText()); } else if (name == xbel::elem_info) { errors += readChildElements(reader, parent); } else if (name == xbel::elem_metadata) { if (reader.attributes().value(xbel::elem_metadata_owner) == QLatin1String{xbel::elem_metadata_owner_value}) parent->metadata.append(reader.readElementText()); else reader.skipCurrentElement(); } else if (name == xbel::elem_bookmark) { auto *item = new BookmarksTreeItem(BookmarksTreeItem::Bookmark, {}, parent); errors += readItemAttributes(item, reader.attributes()); parent->appendChild(item); errors += readChildElements(reader, item); } else if (name == xbel::elem_folder) { auto *item = new BookmarksTreeItem(BookmarksTreeItem::Folder, {}, parent); errors += readItemAttributes(item, reader.attributes()); parent->appendChild(item); errors += readChildElements(reader, item); } else if (name == xbel::elem_separator) { auto *item = new BookmarksTreeItem(BookmarksTreeItem::Separator, {}, parent); parent->appendChild(item); reader.skipCurrentElement(); } else if (name == xbel::elem_alias) { auto *item = new BookmarksTreeItem(BookmarksTreeItem::Alias, {}, parent); item->setData(BookmarksTreeItem::Id, reader.attributes().value(xbel::attr_ref).toString()); parent->appendChild(item); reader.skipCurrentElement(); } else { errors.append({QObject::tr("%1 skipping unknown element '%2' on line %3") .arg(__PRETTY_FUNCTION__, name.toString(), QString::number(reader.lineNumber()))}); reader.skipCurrentElement(); } } return errors; } QList xbel::read(QIODevice *device, BookmarksTreeItem *root) { if (!device->isOpen()) { return {QObject::tr("%1 buffer is not open").arg(__PRETTY_FUNCTION__)}; } QXmlStreamReader reader(device); if (!reader.readNextStartElement()) return {}; // parse the top-level item attributes if (reader.name().compare(QLatin1String{elem_xbel}) != 0) return {QObject::tr("%1 Top-level item is not xbel").arg(__PRETTY_FUNCTION__)}; QList errors; errors += readItemAttributes(root, reader.attributes()); errors += readChildElements(reader, root); return errors; }