DOM Bookmarks Application▲
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 Bookmarks Example} 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 &
amp;element,
QTreeWidgetItem *
parentItem =
nullptr
);
QTreeWidgetItem *
createItem(const
QDomElement &
amp;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, and QStyle::SP_FileIcon correspond to standard pixmaps that follow the style of your GUI.
XbelTree::
XbelTree(QWidget *
parent)
:
QTreeWidget(parent)
{
header()-&
gt;setSectionResizeMode(QHeaderView::
Stretch);
setHeaderLabels({
tr("Title"
), tr("Location"
)}
);
folderIcon.addPixmap(style()-&
gt;standardPixmap(QStyle::
SP_DirClosedIcon),
QIcon::
Normal, QIcon::
Off);
folderIcon.addPixmap(style()-&
gt;standardPixmap(QStyle::
SP_DirOpenIcon),
QIcon::
Normal, QIcon::
On);
bookmarkIcon.addPixmap(style()-&
gt;standardPixmap(QStyle::
SP_FileIcon));
}
The read() function opens the given QIODevice using 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)
&
amp;&
amp; 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
, &
amp;QTreeWidget::
itemChanged, this
, &
amp;XbelTree::
updateDomElement);
QDomElement child =
root.firstChildElement(folderElement);
while
(!
child.isNull()) {
parseFolderElement(child);
child =
child.nextSiblingElement(folderElement);
}
connect(this
, &
amp;QTreeWidget::
itemChanged, this
, &
amp;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 &
amp;element,
QTreeWidgetItem *
parentItem)
{
QTreeWidgetItem *
item =
createItem(element, parentItem);
QString title =
element.firstChildElement(titleElement).text();
if
(title.isEmpty())
title =
tr("Folder"
);
item-&
gt;setFlags(item-&
gt;flags() |
Qt::
ItemIsEditable);
item-&
gt;setIcon(0
, folderIcon);
item-&
gt;setText(0
, title);
bool
folded =
(element.attribute(foldedAttribute) !=
"no"
_L1);
item-&
gt;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-&
gt;setFlags(item-&
gt;flags() |
Qt::
ItemIsEditable);
childItem-&
gt;setIcon(0
, bookmarkIcon);
childItem-&
gt;setText(0
, title);
childItem-&
gt;setText(1
, child.attribute(hrefAttribute));
}
else
if
(child.tagName() ==
"separator"
_L1) {
QTreeWidgetItem *
childItem =
createItem(child, item);
childItem-&
gt;setFlags(item-&
gt;flags() &
amp; ~
(Qt::
ItemIsSelectable |
Qt::
ItemIsEditable));
childItem-&
gt;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 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
createMenus();
XbelTree *
xbelTree;
}
;
The MainWindow Class Implementation▲
The MainWindow constructor instantiates the member XbelTree object, and sets its header with a QStringList object, 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()-&
gt;showMessage(tr("Ready"
));
setWindowTitle(tr("DOM Bookmarks"
));
const
QSize availableSize =
screen()-&
gt;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()-&
gt;addMenu(tr("&File"
));
QAction *
openAct =
fileMenu-&
gt;addAction(tr("&Open..."
), this
, &
amp;MainWindow::
open);
openAct-&
gt;setShortcuts(QKeySequence::
Open);
QAction *
saveAsAct =
fileMenu-&
gt;addAction(tr("&Save As..."
), this
, &
amp;MainWindow::
saveAs);
saveAsAct-&
gt;setShortcuts(QKeySequence::
SaveAs);
QAction *
exitAct =
fileMenu-&
gt;addAction(tr("E&xit"
), this
, &
amp;QWidget::
close);
exitAct-&
gt;setShortcuts(QKeySequence::
Quit);
menuBar()-&
gt;addSeparator();
QMenu *
helpMenu =
menuBar()-&
gt;addMenu(tr("&Help"
));
helpMenu-&
gt;addAction(tr("&About"
), this
, &
amp;MainWindow::
about);
helpMenu-&
gt;addAction(tr("About &Qt"
), qApp, &
amp;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-&
gt;read(&
amp;file))
statusBar()-&
gt;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-&
gt;write(&
amp;file))
statusBar()-&
gt;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."
));
}
See the XML Bookmark Exchange Language Resource Page for more information about XBEL files.