QXmlStream Lesezeichen Beispiel

Zeigt, wie man XBEL-Dateien liest und schreibt.

Das QXmlStream Bookmarks Beispiel bietet einen Viewer für XML Bookmark Exchange Language (XBEL) Dateien. Es kann Lesezeichen mit Hilfe von Qt's QXmlStreamReader lesen und sie mit QXmlStreamWriter wieder ausgeben. Da dieses Beispiel zeigen soll, wie man diese Lese- und Schreibtypen verwendet, bietet es keine Möglichkeit, ein Lesezeichen zu öffnen, ein neues hinzuzufügen oder zwei Lesezeichendateien zusammenzuführen, und nur minimale Möglichkeiten, Lesezeichen zu bearbeiten. Nichtsdestotrotz könnte es sicherlich mit solchen Funktionen erweitert werden, falls gewünscht.

Definition der Klasse XbelWriter

Die Klasse XbelWriter nimmt eine tree widget auf, die eine Hierarchie von Ordnern mit Lesezeichen beschreibt. Ihre writeFile() bietet die Möglichkeit, diese Hierarchie im XBEL-Format auf ein bestimmtes Ausgabegerät zu schreiben.

Intern zeichnet sie das Baum-Widget auf, das ihr gegeben wurde, und verpackt eine private Instanz von QXmlStreamWriter, die ihr die Mittel zum Streamen von XML zur Verfügung stellt. Er hat eine interne writeItem(), um jedes Element in seinem Baum zu schreiben.

class XbelWriter
{
public:
    explicit XbelWriter(const QTreeWidget *treeWidget);
    bool writeFile(QIODevice *device);

private:
    void writeItem(const QTreeWidgetItem *item);
    QXmlStreamWriter xml;
    const QTreeWidget *treeWidget;
};

Implementierung der Klasse XbelWriter

Der XbelWriter Konstruktor akzeptiert die treeWidget, die er beschreiben wird. Er speichert sie und aktiviert die Eigenschaft QXmlStreamWriter für die automatische Formatierung. Letztere teilt die Daten in mehrere Zeilen auf, mit Einrückung, um die Struktur des Baums anzuzeigen, was die XML-Ausgabe leichter lesbar macht.

XbelWriter::XbelWriter(const QTreeWidget *treeWidget) : treeWidget(treeWidget)
{
    xml.setAutoFormatting(true);
}

Die Funktion writeFile() nimmt ein Objekt QIODevice entgegen und weist ihr Mitglied QXmlStreamWriter an, mit setDevice() in dieses Gerät zu schreiben. Diese Funktion schreibt dann die Dokumenttypdefinition (DTD), das Startelement, die Version und delegiert das Schreiben der einzelnen Elemente der obersten Ebene von treeWidget an writeItem(). Schließlich schließt sie das Dokument und kehrt zurück.

bool XbelWriter::writeFile(QIODevice *device)
{
    xml.setDevice(device);

    xml.writeStartDocument();
    xml.writeDTD("<!DOCTYPE xbel>"_L1);
    xml.writeStartElement("xbel"_L1);
    xml.writeAttribute("version"_L1, "1.0"_L1);
    for (int i = 0; i < treeWidget->topLevelItemCount(); ++i)
        writeItem(treeWidget->topLevelItem(i));

    xml.writeEndDocument();
    return true;
}

Die Funktion writeItem() akzeptiert ein QTreeWidgetItem Objekt und schreibt in ihren XML-Stream eine Darstellung des Objekts, die von ihrem UserRole abhängt, das eines von "folder", "bookmark" oder "separator" sein kann. Innerhalb jedes Ordners ruft sie sich selbst rekursiv für jedes untergeordnete Element auf, um rekursiv eine Darstellung jedes untergeordneten Elements in das XML-Element des Ordners aufzunehmen.

void XbelWriter::writeItem(const QTreeWidgetItem *item)
{
    QString tagName = item->data(0, Qt::UserRole).toString();
    if (tagName == "folder"_L1) {
        bool folded = !item->isExpanded();
        xml.writeStartElement(tagName);
        xml.writeAttribute("folded"_L1, folded ? "yes"_L1 : "no"_L1);
        xml.writeTextElement("title"_L1, item->text(0));
        for (int i = 0; i < item->childCount(); ++i)
            writeItem(item->child(i));
        xml.writeEndElement();
    } else if (tagName == "bookmark"_L1) {
        xml.writeStartElement(tagName);
        if (!item->text(1).isEmpty())
            xml.writeAttribute("href"_L1, item->text(1));
        xml.writeTextElement("title"_L1, item->text(0));
        xml.writeEndElement();
    } else if (tagName == "separator"_L1) {
        xml.writeEmptyElement(tagName);
    }
}

Definition der Klasse XbelReader

Die Klasse XbelReader nimmt ein tree widget auf, um es mit Elementen zu füllen, die eine Lesezeichenhierarchie beschreiben. Sie unterstützt das Lesen von XBEL-Daten aus einer QIODevice als Quelle für diese Elemente. Wenn das Parsen der XBEL-Daten fehlschlägt, kann sie melden, was falsch gelaufen ist.

Intern zeichnet sie die QTreeWidget auf, die sie befüllen wird, und packt eine Instanz von QXmlStreamReader, der Begleitklasse von QXmlStreamWriter, die sie zum Lesen von XBEL-Daten verwenden wird.

class XbelReader
{
public:
    XbelReader(QTreeWidget *treeWidget);

    bool read(QIODevice *device);
    QString errorString() const;

private:
    void readXBEL();
    void readTitle(QTreeWidgetItem *item);
    void readSeparator(QTreeWidgetItem *item);
    void readFolder(QTreeWidgetItem *item);
    void readBookmark(QTreeWidgetItem *item);

    QTreeWidgetItem *createChildItem(QTreeWidgetItem *item);

    QXmlStreamReader xml;
    QTreeWidget *treeWidget;

    QIcon folderIcon;
    QIcon bookmarkIcon;
};

Implementierung der Klasse XbelReader

Da sich der XBEL-Reader nur mit dem Lesen von XML-Elementen befasst, macht er ausgiebig Gebrauch von der Komfortfunktion readNextStartElement().

Der XbelReader -Konstruktor benötigt eine QTreeWidget, die er ausfüllt. Er füllt den Stil des Baum-Widgets mit geeigneten Symbolen: ein Ordnersymbol, das seine Form ändert, um anzuzeigen, ob ein Ordner geöffnet oder geschlossen ist, und ein Standard-Dateisymbol für die einzelnen Lesezeichen in diesen Ordnern.

XbelReader::XbelReader(QTreeWidget *treeWidget) : treeWidget(treeWidget)
{
    QStyle *style = treeWidget->style();

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

Die Funktion read() akzeptiert eine QIODevice. Sie weist ihr Mitglied QXmlStreamReader an, den Inhalt von diesem Gerät zu lesen. Beachten Sie, dass die XML-Eingabe wohlgeformt sein muss, damit sie von QXmlStreamReader akzeptiert wird. Zunächst wird die äußere Struktur gelesen und überprüft, ob es sich bei dem Inhalt um eine XBEL 1.0-Datei handelt; ist dies der Fall, delegiert read() das eigentliche Lesen des Inhalts an die interne readXBEL().

Andernfalls wird die Funktion raiseError() verwendet, um eine Fehlermeldung aufzuzeichnen. Das Lesegerät selbst kann dies ebenfalls tun, wenn es auf Fehler in der Eingabe stößt. Wenn read() fertig ist, gibt sie true zurück, wenn keine Fehler aufgetreten sind.

bool XbelReader::read(QIODevice *device)
{
    xml.setDevice(device);

    if (xml.readNextStartElement()) {
        if (xml.name() == "xbel"_L1 && xml.attributes().value("version"_L1) == "1.0"_L1)
            readXBEL();
        else
            xml.raiseError(QObject::tr("The file is not an XBEL version 1.0 file."));
    }

    return !xml.error();
}

Wenn read() false zurückgibt, kann der Aufrufer eine Beschreibung des Fehlers mit Zeilen- und Spaltennummer innerhalb des Datenstroms erhalten, indem er die Funktion errorString() aufruft.

QString XbelReader::errorString() const
{
    return QObject::tr("%1\nLine %2, column %3")
            .arg(xml.errorString())
            .arg(xml.lineNumber())
            .arg(xml.columnNumber());
}

Die Funktion readXBEL() liest den Namen eines Startelements und ruft die entsprechende Funktion auf, um es zu lesen, je nachdem, ob sein Tag-Name "folder", "bookmark" oder "separator" lautet. Alle anderen Elemente, die gefunden werden, werden übersprungen. Die Funktion beginnt mit einer Vorbedingung, mit der überprüft wird, ob der XML-Reader gerade ein "xbel" Element geöffnet hat.

void XbelReader::readXBEL()
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "xbel"_L1);

    while (xml.readNextStartElement()) {
        if (xml.name() == "folder"_L1)
            readFolder(nullptr);
        else if (xml.name() == "bookmark"_L1)
            readBookmark(nullptr);
        else if (xml.name() == "separator"_L1)
            readSeparator(nullptr);
        else
            xml.skipCurrentElement();
    }
}

Die Funktion readBookmark() erstellt ein neues editierbares Element, das ein einzelnes Lesezeichen darstellt. Sie nimmt das XML-Attribut "href" des aktuellen Elements als zweiten Spaltentext des Elements auf und setzt dessen ersten Spaltentext provisorisch auf "Unknown title", bevor sie den Rest des Elements nach einem Titelelement durchsucht, um dieses zu überschreiben, wobei sie alle nicht erkannten Kindelemente überspringt.

void XbelReader::readTitle(QTreeWidgetItem *item)
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "title"_L1);
    item->setText(0, xml.readElementText());
}

Die Funktion readTitle() liest den Titel eines Lesezeichens und speichert ihn als Titel (Text in der ersten Spalte) des Elements, für das sie aufgerufen wurde.

void XbelReader::readSeparator(QTreeWidgetItem *item)
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1);
    constexpr char16_t midDot = u'\xB7';
    static const QString dots(30, midDot);

    QTreeWidgetItem *separator = createChildItem(item);
    separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{});
    separator->setText(0, dots);
    xml.skipCurrentElement();
}

Die Funktion readSeparator() erstellt ein Trennelement und setzt dessen Flags. Der Text des Trennelements wird auf 30 zentrierte Punkte gesetzt. Der Rest des Elements wird dann mit skipCurrentElement() übersprungen.

void XbelReader::readSeparator(QTreeWidgetItem *item)
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "separator"_L1);
    constexpr char16_t midDot = u'\xB7';
    static const QString dots(30, midDot);

    QTreeWidgetItem *separator = createChildItem(item);
    separator->setFlags(item ? item->flags() & ~Qt::ItemIsSelectable : Qt::ItemFlags{});
    separator->setText(0, dots);
    xml.skipCurrentElement();
}

Die Funktion readFolder() erstellt ein Element und durchläuft den Inhalt des Ordnerelements, wobei diesem Element Kinder hinzugefügt werden, die den Inhalt des Ordnerelements darstellen. Die Schleife über den Inhalt des Ordners hat eine ähnliche Form wie die in readXBEL(), nur dass sie jetzt ein title-Element akzeptiert, um den Titel des Ordners festzulegen.

void XbelReader::readFolder(QTreeWidgetItem *item)
{
    Q_ASSERT(xml.isStartElement() && xml.name() == "folder"_L1);

    QTreeWidgetItem *folder = createChildItem(item);
    bool folded = xml.attributes().value("folded"_L1) != "no"_L1;
    folder->setExpanded(!folded);

    while (xml.readNextStartElement()) {
        if (xml.name() == "title"_L1)
            readTitle(folder);
        else if (xml.name() == "folder"_L1)
            readFolder(folder);
        else if (xml.name() == "bookmark"_L1)
            readBookmark(folder);
        else if (xml.name() == "separator"_L1)
            readSeparator(folder);
        else
            xml.skipCurrentElement();
    }
}

Die createChildItem() Hilfsfunktion erzeugt ein neues Tree-Widget-Element, das entweder ein Kind des angegebenen Elements ist oder, wenn kein übergeordnetes Element angegeben ist, ein direktes Kind des Tree-Widgets. Sie setzt den UserRole des neuen Elements auf den Tag-Namen des aktuellen XML-Elements, passend dazu, wie XbelWriter::writeFile() diesen UserRole verwendet.

QTreeWidgetItem *XbelReader::createChildItem(QTreeWidgetItem *item)
{
    QTreeWidgetItem *childItem = item ? new QTreeWidgetItem(item) : new QTreeWidgetItem(treeWidget);
    childItem->setData(0, Qt::UserRole, xml.name().toString());
    return childItem;
}

Definition der MainWindow-Klasse

Die Klasse MainWindow ist eine Unterklasse von QMainWindow, mit einem Menü File und einem Menü Help.

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();

public slots:
    void open();
    void saveAs();
    void about();
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
    void onCustomContextMenuRequested(const QPoint &pos);
#endif
private:
    void createMenus();

    QTreeWidget *const treeWidget;
};

Implementierung der MainWindow-Klasse

Der MainWindow -Konstruktor richtet sein QTreeWidget -Objekt, treeWidget, als eigenes zentrales Widget ein, mit Spaltenüberschriften für den Titel und die Position jeder Buchmarke. Er konfiguriert ein benutzerdefiniertes Menü, das es dem Benutzer ermöglicht, Aktionen für einzelne Lesezeichen innerhalb des Baum-Widgets durchzuführen.

Es ruft createMenus() auf, um seine eigenen Menüs und die entsprechenden Aktionen einzurichten. Es setzt seinen Titel, meldet sich selbst als bereit und stellt seine Größe auf einen angemessenen Anteil des verfügbaren Bildschirmplatzes ein.

MainWindow::MainWindow() : treeWidget(new QTreeWidget)
{
    treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch);
    treeWidget->setHeaderLabels(QStringList{tr("Title"), tr("Location")});
#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
    treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(treeWidget, &QWidget::customContextMenuRequested,
            this, &MainWindow::onCustomContextMenuRequested);
#endif
    setCentralWidget(treeWidget);

    createMenus();

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

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

Ein benutzerdefiniertes Menü, das ausgelöst wird, wenn der Benutzer mit der rechten Maustaste auf ein Lesezeichen klickt, ermöglicht es, das Lesezeichen als Link zu kopieren oder einen Desktop-Browser anzuweisen, die URL zu öffnen, auf die es verweist. Dieses Menü wird (wenn die entsprechenden Funktionen aktiviert sind) von onCustomContextMenuRequested() implementiert.

#if QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)
void MainWindow::onCustomContextMenuRequested(const QPoint &pos)
{
    const QTreeWidgetItem *item = treeWidget->itemAt(pos);
    if (!item)
        return;
    const QString url = item->text(1);
    QMenu contextMenu;
    QAction *copyAction = contextMenu.addAction(tr("Copy Link to Clipboard"));
    QAction *openAction = contextMenu.addAction(tr("Open"));
    QAction *action = contextMenu.exec(treeWidget->viewport()->mapToGlobal(pos));
    if (action == copyAction)
        QGuiApplication::clipboard()->setText(url);
    else if (action == openAction)
        QDesktopServices::openUrl(QUrl(url));
}
#endif // QT_CONFIG(clipboard) && QT_CONFIG(contextmenu)

Die Funktion createMenus() erstellt die Objekte fileMenu und helpMenu und fügt ihnen QAction hinzu, die auf verschiedene Weise mit den Funktionen open(), saveAs() und about() sowie QWidget::close() und QApplication::aboutQt() verbunden sind. Die Verbindungen sind wie unten dargestellt:

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

Auf diese Weise wird das in den folgenden Screenshots gezeigte Menü erstellt:

Wenn die Funktion open() ausgelöst wird, bietet sie dem Benutzer einen Dateidialog zur Auswahl einer Lesezeichendatei an. Wenn eine Datei ausgewählt ist, wird sie mit Hilfe von XBelReader geparst, um die treeWidget mit Lesezeichen zu füllen. Treten Probleme beim Öffnen oder Parsen der Datei auf, erhält der Benutzer eine entsprechende Warnmeldung mit Dateiname und Fehlermeldung. Andernfalls werden die aus der Datei gelesenen Lesezeichen angezeigt und die Statusleiste des Fensters meldet kurz, dass die Datei geladen wurde.

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

    treeWidget->clear();

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

    XbelReader reader(treeWidget);
    if (!reader.read(&file)) {
        QMessageBox::warning(
                this, tr("QXmlStream Bookmarks"),
                tr("Parse error in file %1:\n\n%2")
                        .arg(QDir::toNativeSeparators(fileName), reader.errorString()));
    } else {
        statusBar()->showMessage(tr("File loaded"), 2000);
    }
}

Die Funktion saveAs() zeigt eine QFileDialog an und fordert den Benutzer auf, eine fileName einzugeben, in der eine Kopie der Lesezeichendaten gespeichert werden soll. Ähnlich wie bei der Funktion open() wird auch bei dieser Funktion eine Warnmeldung angezeigt, wenn die Datei nicht beschrieben werden kann.

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("QXmlStream Bookmarks"),
                             tr("Cannot write file %1:\n%2.")
                                     .arg(QDir::toNativeSeparators(fileName), file.errorString()));
        return;
    }

    XbelWriter writer(treeWidget);
    if (writer.writeFile(&file))
        statusBar()->showMessage(tr("File saved"), 2000);
}

Die Funktion about() zeigt eine QMessageBox mit einer kurzen Beschreibung des Beispiels oder allgemeinen Informationen über Qt und die verwendete Version an.

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

main() Funktion

Die Funktion main() instanziiert MainWindow und ruft die Funktion show() auf, um sie anzuzeigen, dann die Funktion open(), da dies höchstwahrscheinlich das ist, was der Benutzer zuerst tun möchte.

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow mainWin;
    mainWin.show();
    mainWin.open();
    return app.exec();
}

Weitere Informationen über XBEL-Dateien finden Sie auf der XML Bookmark Exchange Language Resource Page.

Beispielprojekt @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.