DOM 书签应用程序

提供 XML 书签交换语言文件的阅读器。

DOM 书签应用程序提供 XML 书签交换语言(XBEL)文件的阅读器,它使用 Qt 的基于 DOM 的 XML API 读取和解析这些文件。QXmlStream 书签示例提供了读取这类文件的另一种方式。

XbelTree 类定义

XbelTree 类具有用于读取和写入文件系统的功能。它继承自 QTreeWidget 类,包含显示书签的模型,并允许对其进行编辑。

class XbelTree : public QTreeWidget
{
    Q_OBJECT

public:
    explicit XbelTree(QWidget *parent = nullptr);

    bool read(QIODevice *device);
    bool write(QIODevice *device) const;

protected:
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
    void contextMenuEvent(QContextMenuEvent *event) override;
#endif

private slots:
    void updateDomElement(const QTreeWidgetItem *item, int column);

private:
    void parseFolderElement(const QDomElement &element,
                            QTreeWidgetItem *parentItem = nullptr);
    QTreeWidgetItem *createItem(const QDomElement &element,
                                QTreeWidgetItem *parentItem = nullptr);

    QDomDocument domDocument;
    QIcon folderIcon;
    QIcon bookmarkIcon;
};

XbelTree 类实现

XbelTree 构造函数接受一个 QWidget,在其中放置该对象。《code translate="no">folderIcon 设置为 QIcon::Normal 模式,此时仅在用户未与图标交互时显示拼图。《a href="qstyle.html#StandardPixmap-enum" translate="no">QStyle::SP_DirClosedIcon,《a href="qstyle.html#StandardPixmap-enum" translate="no">QStyle::SP_DirOpenIcon 和 《a href="qstyle.html#StandardPixmap-enum" translate="no">QStyle::SP_FileIcon 对应于遵循您 GUI 风格的标准位图。

XbelTree::XbelTree(QWidget *parent)
    : QTreeWidget(parent)
{
    header()->setSectionResizeMode(QHeaderView::Stretch);
    setHeaderLabels({tr("Title"), tr("Location")});

    folderIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon),
                         QIcon::Normal, QIcon::Off);
    folderIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon),
                         QIcon::Normal, QIcon::On);
    bookmarkIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon));
}

read() 函数使用 QDomDocument::setContent 打开给定的 QIODevice。如果成功打开文件并验证顶层标题,则在解析文件内容之前,通过迭代所有顶层 XML 节点并对每个节点调用 parseFolderElement(),将类的全部内容清除。

bool XbelTree::read(QIODevice *device)
{
    QDomDocument::ParseResult result =
            domDocument.setContent(device, QDomDocument::ParseOption::UseNamespaceProcessing);
    if (!result) {
        QMessageBox::information(window(), tr("DOM Bookmarks"),
                                 tr("Parse error at line %1, column %2:\n%3")
                                         .arg(result.errorLine)
                                         .arg(result.errorColumn)
                                         .arg(result.errorMessage));
        return false;
    }

    QDomElement root = domDocument.documentElement();
    if (root.tagName() != "xbel") {
        QMessageBox::information(window(), tr("DOM Bookmarks"),
                                 tr("The file is not an XBEL file."));
        return false;
    } else if (root.hasAttribute(versionAttribute)
               && root.attribute(versionAttribute) != "1.0"_L1) {
        QMessageBox::information(window(), tr("DOM Bookmarks"),
                                 tr("The file is not an XBEL version 1.0 "
                                    "file."));
        return false;
    }

    clear();

    disconnect(this, &QTreeWidget::itemChanged, this, &XbelTree::updateDomElement);

    QDomElement child = root.firstChildElement(folderElement);
    while (!child.isNull()) {
        parseFolderElement(child);
        child = child.nextSiblingElement(folderElement);
    }

    connect(this, &QTreeWidget::itemChanged, this, &XbelTree::updateDomElement);

    return true;
}

parseFolderElement() 函数处理不同的元素类型,如果元素是子文件夹,它会递归调用自身。

void XbelTree::parseFolderElement(const QDomElement &element,
                                  QTreeWidgetItem *parentItem)
{
    QTreeWidgetItem *item = createItem(element, parentItem);

    QString title = element.firstChildElement(titleElement).text();
    if (title.isEmpty())
        title = tr("Folder");

    item->setFlags(item->flags() | Qt::ItemIsEditable);
    item->setIcon(0, folderIcon);
    item->setText(0, title);

    bool folded = (element.attribute(foldedAttribute) != "no"_L1);
    item->setExpanded(!folded);

    constexpr char16_t midDot = u'\xB7';
    static const QString dots = QString(30, midDot);
    QDomElement child = element.firstChildElement();
    while (!child.isNull()) {
        if (child.tagName() == folderElement) {
            parseFolderElement(child, item);
        } else if (child.tagName() == bookmarkElement) {
            QTreeWidgetItem *childItem = createItem(child, item);

            QString title = child.firstChildElement(titleElement).text();
            if (title.isEmpty())
                title = tr("Folder");

            childItem->setFlags(item->flags() | Qt::ItemIsEditable);
            childItem->setIcon(0, bookmarkIcon);
            childItem->setText(0, title);
            childItem->setText(1, child.attribute(hrefAttribute));
        } else if (child.tagName() == "separator"_L1) {
            QTreeWidgetItem *childItem = createItem(child, item);
            childItem->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEditable));
            childItem->setText(0, dots);
        }
        child = child.nextSiblingElement();
    }
}

write() 函数使用 QIODeviceQIODevice 将 domDocument 保存下来。

bool XbelTree::write(QIODevice *device) const
{
    const int IndentSize = 4;

    QTextStream out(device);
    domDocument.save(out, IndentSize);
    return true;
}

MainWindow 类定义

MainWindow 类是 QMainWindow 的子类,具有 File 菜单和 Help 菜单。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

public slots:
    void open();
    void saveAs();
    void about();

private:
    void createMenus();

    XbelTree *xbelTree;
};

MainWindow 类实现

MainWindow 构造函数实例化成员 XbelTree 对象,并使用 QStringList 对象 labels 设置其标题。构造函数还调用 createMenus() 来设置菜单。使用 statusBar() 显示消息“就绪”。

MainWindow::MainWindow()
{
    xbelTree = new XbelTree;
    setCentralWidget(xbelTree);

    createMenus();

    statusBar()->showMessage(tr("Ready"));

    setWindowTitle(tr("DOM Bookmarks"));
    const QSize availableSize = screen()->availableGeometry().size();
    resize(availableSize.width() / 2, availableSize.height() / 3);
}

createMenus() 函数填充菜单并设置键盘快捷键。

void MainWindow::createMenus()
{
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
    QAction *openAct = fileMenu->addAction(tr("&Open..."), this, &MainWindow::open);
    openAct->setShortcuts(QKeySequence::Open);

    QAction *saveAsAct = fileMenu->addAction(tr("&Save As..."), this, &MainWindow::saveAs);
    saveAsAct->setShortcuts(QKeySequence::SaveAs);

    QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close);
    exitAct->setShortcuts(QKeySequence::Quit);

    menuBar()->addSeparator();

    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
    helpMenu->addAction(tr("&About"), this, &MainWindow::about);
    helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
}

open() 函数允许用户使用 QFileDialog 打开 XBEL 文件。如果文件无法读取或存在解析错误,则会显示警告消息,以及《code translate="no">fileName 和 errorString。如果成功,它会调用 XbelTree::read()

void MainWindow::open()
{
    QFileDialog fileDialog(this, tr("Open Bookmark File"), QDir::currentPath());
    fileDialog.setMimeTypeFilters({"application/x-xbel"_L1});
    if (fileDialog.exec() != QDialog::Accepted)
        return;

    const QString fileName = fileDialog.selectedFiles().constFirst();
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("DOM Bookmarks"),
                             tr("Cannot read file %1:\n%2.")
                             .arg(QDir::toNativeSeparators(fileName),
                                  file.errorString()));
        return;
    }

    if (xbelTree->read(&file))
        statusBar()->showMessage(tr("File loaded"), 2000);
}

函数 saveAs() 会显示一个 QFileDialog 对话框,提示用户输入文件名 fileName。类似于 open() 函数,如果文件无法写入,该功能也会显示警告信息。如果成功,它会调用 XbelTree::write()

void MainWindow::saveAs()
{
    QFileDialog fileDialog(this, tr("Save Bookmark File"), QDir::currentPath());
    fileDialog.setAcceptMode(QFileDialog::AcceptSave);
    fileDialog.setDefaultSuffix("xbel"_L1);
    fileDialog.setMimeTypeFilters({"application/x-xbel"_L1});
    if (fileDialog.exec() != QDialog::Accepted)
        return;

    const QString fileName = fileDialog.selectedFiles().constFirst();
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("DOM Bookmarks"),
                             tr("Cannot write file %1:\n%2.")
                             .arg(QDir::toNativeSeparators(fileName),
                                  file.errorString()));
        return;
    }

    if (xbelTree->write(&file))
        statusBar()->showMessage(tr("File saved"), 2000);
}

函数 about() 会显示一个包含示例简要描述的 QMessageBox

void MainWindow::about()
{
   QMessageBox::about(this, tr("About DOM Bookmarks"),
                      tr("The <b>DOM Bookmarks</b> example demonstrates how to "
                         "use Qt's DOM classes to read and write XML "
                         "documents."));
}

有关 XBEL 文件更多信息,请参阅 XMLBookmark Exchange Language资源页

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。本文件中包含的文档贡献为各自所有者的版权。提供的文档受GNU自由文档许可协议版本1.3的条款约束,由自由软件基金会发布。Qt及其相关标志是芬兰和其他国家的 Qt公司有限责任公司的商标。所有其他商标均为其各自所有者的财产。