DOM Bookmarks Application

提供用于 XML 书签交换语言文件的读取器。

The DOM Bookmarks Application provides a reader for XML Bookmark Exchange Language (XBEL) files that uses Qt's DOM-based XML API to read and parse the files. The QXmlStream 书签范例 provides an alternative way to read this type of file.

The XbelTree Class Definition

The XbelTree class has functions for reading and writing to the filesystem. It inherits from the QTreeWidget class, contains the model for the displaying of the bookmarks, and allows it to be edited.

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;
};
					

The XbelTree Class Implementation

The XbelTree constructor accepts a QWidget within which it is placed. The folderIcon is set to QIcon::Normal mode where the pixmap is only displayed when the user is not interacting with the icon. The QStyle::SP_DirClosedIcon , QStyle::SP_DirOpenIcon ,和 QStyle::SP_FileIcon correspond to standard pixmaps that follow the style of your 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));
}
					

The read() function opens the given QIODevice 使用 QDomDocument::setContent . If it succeeds opening the file and the top level headers are verified, the contents of the class is cleared before the file contents is parsed by iterating all the top level XML nodes and calling parseFolderElement() on each of them.

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;
}
					

The parseFolderElement() function handles the different element types and calls itself recursively if the element is a subfolder.

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();
    }
}
					

The write() function saves the domDocument to the given QIODevice using QDomDocument::save.

bool XbelTree::write(QIODevice *device) const
{
    const int IndentSize = 4;
    QTextStream out(device);
    domDocument.save(out, IndentSize);
    return true;
}
					

The MainWindow Class Definition

The MainWindow 类是子类化的 QMainWindow ,采用 File menu and a 帮助 菜单。

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow();
public slots:
    void open();
    void saveAs();
    void about();
private:
    void createMenus();
    XbelTree *xbelTree;
};
					

The MainWindow Class Implementation

The MainWindow constructor instantiates the member XbelTree object, and sets its header with a QStringList 对象, labels . The constructor also invokes createMenus() to set up the menus. The statusBar() is used to display the message "Ready".

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);
}
					

The createMenus() function populates the menus and sets keyboard shortcuts.

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);
}
					

The open() function enables the user to open an XBEL file using QFileDialog . A warning message is displayed along with the fileName and errorString if the file cannot be read or if there is a parse error. If it succeeds it calls 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);
}
					

The saveAs() function displays a QFileDialog , prompting the user for a fileName . Similar to the open() function, this function also displays a warning message if the file cannot be written to. If this succeeds it calls 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);
}
					

The about() function displays a QMessageBox with a brief description of the example.

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."));
}
					

XML 书签交换语言资源页面 了解有关 XBEL 文件的更多信息。

范例工程 @ code.qt.io