Qt 4.3: QXmlStream Bookmarks Example

|  | Home · All Classes · Main Classes · Grouped Classes · Modules · Functions |

# QXmlStream Bookmarks Example

Files:

- xml/streambookmarks/mainwindow.cpp
- xml/streambookmarks/mainwindow.h
- xml/streambookmarks/xbelreader.cpp
- xml/streambookmarks/xbelreader.h
- xml/streambookmarks/xbelwriter.cpp
- xml/streambookmarks/xbelwriter.h
- xml/streambookmarks/main.cpp

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.

     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.

     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".

     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.

     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.

     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.

     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()`.

     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.

     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()`.

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

     private 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.

     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.

     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.

     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.

     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:

     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:

     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

The `main()` function instantiates `MainWindow` and invokes the `show()` function.

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

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