|  | Home · Overviews · Examples

# QXmlStream Bookmarks Example

The QXmlStream Bookmarks example provides a reader for XML Bookmark Exchange Language (XBEL) files using Qt's QXmlStreamReader class for reading, and QXmlStreamWriter class for writing the files.

## XbelWriter Class Definition

The `XbelWriter` class is a subclass of QXmlStreamReader, which provides an XML parser with a streaming API. `XbelWriter` also contains a private instance of QTreeWidget in order to display the bookmarks according to hierarchies.

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

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

## XbelWriter Class Implementation

The `XbelWriter` constructor accepts a `treeWidget` to initialize within its definition. We enable QXmlStreamWriter's auto-formatting property to ensure line-breaks and indentations are added automatically to empty sections between elements, increasing readability as the data is split into several lines.
The following code example is written in c++.

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

The `writeFile()` function accepts a QIODevice object and sets it using `setDevice()`. This function then writes the document type definition(DTD), the start element, the version, and `treeWidget`'s top-level items.
The following code example is written in c++.

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

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

        writeEndDocument();
        return true;
    }

The `writeItem()` function accepts a QTreeWidget object and writes it to the stream, depending on its `tagName`, which can either be a "folder", "bookmark", or "separator".
The following code example is written in c++.

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

## XbelReader Class Definition

The `XbelReader` class is a subclass of QXmlStreamReader, the pendent class for QXmlStreamWriter. `XbelReader` contains a private instance of QTreeWidget to group bookmarks according to their hierarchies.

    class XbelReader : public QXmlStreamReader
    {
    public:

        XbelReader(QTreeWidget *treeWidget);

        bool read(QIODevice *device);

    private:

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

        QTreeWidgetItem *createChildItem(QTreeWidgetItem *item);

        QTreeWidget *treeWidget;

        QIcon folderIcon;
        QIcon bookmarkIcon;
    };

## XbelReader Class Implementation

The `XbelReader` constructor accepts a QTreeWidget to initialize the `treeWidget` within its definition. A QStyle object is used to set `treeWidget`'s style property. 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, and QStyle::SP_FileIcon correspond to standard pixmaps that follow the style of your GUI.
The following code example is written in c++.

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

The `read()` function accepts a QIODevice and sets it using setDevice(). The actual process of reading only takes place in event the file is a valid XBEL 1.0 file. Otherwise, the raiseError() function is used to display an error message.
The following code example is written in c++.

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

        while (!atEnd()) {
            readNext();

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

        return !error();
    }

The `readUnknownElement()` function reads an unknown element. The Q_ASSERT() macro is used to provide a pre-condition for the function.
The following code example is written in c++.

    void XbelReader::readUnknownElement()
    {
        Q_ASSERT(isStartElement());

        while (!atEnd()) {
            readNext();

            if (isEndElement())
                break;

            if (isStartElement())
                readUnknownElement();
        }
    }

The `readXBEL()` function reads the name of a startElement and calls the appropriate function to read it, depending on whether if its a "folder", "bookmark" or "separator". Otherwise, it calls `readUnknownElement()`.
The following code example is written in c++.

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

        while (!atEnd()) {
            readNext();

            if (isEndElement())
                break;

            if (isStartElement()) {
                if (name() == "folder")
                    readFolder(0);
                else if (name() == "bookmark")
                    readBookmark(0);
                else if (name() == "separator")
                    readSeparator(0);
                else
                    readUnknownElement();
            }
        }
    }

The `readTitle()` function reads the bookmark's title.
The following code example is written in c++.

    void XbelReader::readTitle(QTreeWidgetItem *item)
    {
        Q_ASSERT(isStartElement() && name() == "title");

        QString title = readElementText();
        item->setText(0, title);
    }

The `readSeparator()` function creates a separator and sets its flags. The text is set to 30 "0xB7", the HEX equivalent for period, and then read using `readElementText()`.
The following code example is written in c++.

    void XbelReader::readSeparator(QTreeWidgetItem *item)
    {
        QTreeWidgetItem *separator = createChildItem(item);
        separator->setFlags(item->flags() & ~Qt::ItemIsSelectable);
        separator->setText(0, QString(30, 0xB7));
        readElementText();
    }

## MainWindow Class Definition

The `MainWindow` class is a subclass of QMainWindow, with a `File` menu and a `Help` menu.

    class MainWindow : public QMainWindow
    {
        Q_OBJECT

    public:
        MainWindow();

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

    private:
        void createActions();
        void createMenus();

        QTreeWidget *treeWidget;

        QMenu *fileMenu;
        QMenu *helpMenu;
        QAction *openAct;
        QAction *saveAsAct;
        QAction *exitAct;
        QAction *aboutAct;
        QAction *aboutQtAct;
    };

## MainWindow Class Implementation

The `MainWindow` constructor instantiates the QTreeWidget object, `treeWidget` and sets its header with a QStringList object, `labels`. The constructor also invokes `createActions()` and `createMenus()` to set up the menus and their corresponding actions. The `statusBar()` is used to display the message "Ready" and the window's size is fixed to 480x320 pixels.
The following code example is written in c++.

    MainWindow::MainWindow()
    {
        QStringList labels;
        labels << tr("Title") << tr("Location");

        treeWidget = new QTreeWidget;
        treeWidget->header()->setResizeMode(QHeaderView::Stretch);
        treeWidget->setHeaderLabels(labels);
        setCentralWidget(treeWidget);

        createActions();
        createMenus();

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

        setWindowTitle(tr("QXmlStream Bookmarks"));
        resize(480, 320);
    }

The `open()` function enables the user to open an XBEL file using QFileDialog::getOpenFileName(). A warning message is displayed along with the `fileName` and `errorString` if the file cannot be read or if there is a parse error.
The following code example is written in c++.

    void MainWindow::open()
    {
        QString fileName =
                QFileDialog::getOpenFileName(this, tr("Open Bookmark File"),
                                             QDir::currentPath(),
                                             tr("XBEL Files (*.xbel *.xml)"));
        if (fileName.isEmpty())
            return;

        treeWidget->clear();

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

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

    }

The `saveAs()` function displays a QFileDialog, prompting the user for a `fileName` using QFileDialog::getSaveFileName(). Similar to the `open()` function, this function also displays a warning message if the file cannot be written to.
The following code example is written in c++.

    void MainWindow::saveAs()
    {
        QString fileName =
                QFileDialog::getSaveFileName(this, tr("Save Bookmark File"),
                                             QDir::currentPath(),
                                             tr("XBEL Files (*.xbel *.xml)"));
        if (fileName.isEmpty())
            return;

        QFile file(fileName);
        if (!file.open(QFile::WriteOnly | QFile::Text)) {
            QMessageBox::warning(this, tr("QXmlStream Bookmarks"),
                                 tr("Cannot write file %1:\n%2.")
                                 .arg(fileName)
                                 .arg(file.errorString()));
            return;
        }

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

The `about()` function displays a QMessageBox with a brief description of the example.
The following code example is written in c++.

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

In order to implement the `open()`, `saveAs()`, `exit()`, `about()` and `aboutQt()` functions, we connect them to QAction objects and add them to the `fileMenu` and `helpMenu`. The connections are as shown below:
The following code example is written in c++.

    void MainWindow::createActions()
    {
        openAct = new QAction(tr("&Open..."), this);
        openAct->setShortcut(tr("Ctrl+O"));
        connect(openAct, SIGNAL(triggered()), this, SLOT(open()));

        saveAsAct = new QAction(tr("&Save As..."), this);
        saveAsAct->setShortcut(tr("Ctrl+S"));
        connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));

        exitAct = new QAction(tr("E&xit"), this);
        exitAct->setShortcut(tr("Ctrl+Q"));
        connect(exitAct, SIGNAL(triggered()), this, SLOT(close()));

        aboutAct = new QAction(tr("&About"), this);
        connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

        aboutQtAct = new QAction(tr("About &Qt"), this);
        connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
    }

The `createMenus()` function creates the `fileMenu` and `helpMenu` and adds the QAction objects to them in order to create the menu shown in the screenshot below:

The following code example is written in c++.

    void MainWindow::createMenus()
    {
        fileMenu = menuBar()->addMenu(tr("&File"));
        fileMenu->addAction(openAct);
        fileMenu->addAction(saveAsAct);
        fileMenu->addAction(exitAct);

        menuBar()->addSeparator();

        helpMenu = menuBar()->addMenu(tr("&Help"));
        helpMenu->addAction(aboutAct);
        helpMenu->addAction(aboutQtAct);
    }

## main() Function

`main()` The `main()` function instantiates `MainWindow` and invokes the `show()` function.
The following code example is written in c++.

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

See the XML Bookmark Exchange Language Resource Page for more information about XBEL files.