QXmlStreamブックマークの例

XBEL ファイルの読み取りおよび書き込み方法を説明します。

QXmlStream Bookmarks のサンプルは、XML Bookmark Exchange Language (XBEL) ファイルのビューアを提供します。Qt のQXmlStreamReader を使ってブックマークを読み込み、QXmlStreamWriter を使って書き戻すことができます。このサンプルは、これらのリーダとライタの使い方を示すことを目的としているため、ブックマークを開いたり、新しいブックマークを追加したり、2つのブックマークファイルをマージしたりすることはできません。とはいえ、必要であれば、そのような機能で拡張できることは確かである。

XbelWriterクラスの定義

XbelWriter クラスは、ブックマークを含むフォルダの階層構造を記述したtree widget を受け取ります。そのwriteFile() 、この階層をXBELフォーマットで指定された出力デバイスに書き出す手段を提供する。

内部的には、与えられたツリー・ウィジェットを記録し、XMLをストリーミングする手段を提供するQXmlStreamWriter のプライベート・インスタンスをパッケージ化する。また、ツリー内の各アイテムを書き込むための内部writeItem()

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

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

XbelWriter クラスの実装

XbelWriter コンストラクタは、記述するtreeWidget を受け入れます。それを格納し、QXmlStreamWriter の自動フォーマット・プロパティを有効にします。これは、データをいくつかの行に分割し、ツリーの構造を示すためにインデントを付けて、XML出力を読みやすくするものです。

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

writeFile() 関数はQIODevice オブジェクトを受け取り、QXmlStreamWriter メンバにsetDevice() を使ってこのデバイスに書き込むように指示する。次にこの関数は、文書型定義(DTD)、開始要素、バージョンを書き込み、treeWidget の各トップレベル項目の書き込みをwriteItem() に委譲する。最後に、文書を閉じて戻ります。

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

writeItem() 関数は、QTreeWidgetItem オブジェクトを受け取り、そのオブジェクトの表現を XML ストリームに書き込みます。このオブジェクトの表現は、UserRole に依存します。 は、"folder""bookmark""separator" のいずれかです。各フォルダ内では、子項目ごとに自分自身を再帰的に呼び出して、フォルダの XML 要素内に各子項目の表現を再帰的に含めます。

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

XbelReader クラス定義

XbelReadertree widget を受け取り、ブックマーク階層を記述する項目を入力します。これらの項目のソースとして、QIODevice からの XBEL データの読み取りをサポートしています。XBELデータの解析に失敗した場合、何が問題だったかを報告することができる。

内部的には、QTreeWidget を記録し、QXmlStreamWriter のコンパニオン・クラスであるQXmlStreamReader のインスタンスをパッケージ化します。

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

XbelReaderクラスの実装

XBELリーダーは、XML要素の読み取りのみを目的としているため、readNextStartElement()便利関数を多用します。

XbelReader コンストラクタは、QTreeWidget を必要とします。これは、ツリーウィジェットのスタイルに適切なアイコンを入力します:各フォルダが開いているか閉じているかを示すために形を変えるフォルダアイコンと、それらのフォルダ内の個々のブックマーク用の標準ファイルアイコンです。

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

read() 関数は、QIODevice を受け取る。それは、QXmlStreamReader メンバに、そのデバイスからコンテンツを読み込むように指示する。XML入力は、QXmlStreamReader によって受け入れられるように、整形式でなければならないことに注意すること。まず、外部構造を読み取り、コンテンツがXBEL 1.0ファイルであることを確認する。XBEL 1.0ファイルである場合、read() は、コンテンツの実際の読み取りを内部のreadXBEL() に委ねる。

そうでない場合は、raiseError ()関数を使用してエラーメッセージを記録する。入力にエラーが発生した場合、リーダー自体も同じことを行う。read() が終了すると、エラーがなければ真を返す。

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

read() が false を返した場合、呼び出し元はerrorString() 関数を呼び出すことで、ストリームの行番号と列番号を含むエラーの説明を得ることができる。

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

readXBEL() 関数は、開始要素の名前を読み取り、そのタグ名が"folder""bookmark""separator" のいずれであるかに応じて、適切な関数を呼び出して読み取ります。この関数は,XMLリーダーが"xbel" 要素をオープンしたことを確認する前提条件から始まります.

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

readBookmark() 関数は、一つのブックマークを表す新しい編集可能な項目を作成する。現在の要素のXML"href" 属性をアイテムの2番目の列テキストとして記録し、その最初の列テキストを暫定的に"Unknown title" に設定した後、要素の残りの部分をスキャンしてそれを上書きする title 要素を探し、認識できない子要素はスキップします。

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

readTitle() 関数はブックマークのタイトルを読み取り、それが呼び出されたアイテムのタイトル(最初の列のテキスト)として記録します。

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

readSeparator() 関数はセパレータを作成し、そのフラグを設定します。セパレーター項目のテキストは中央揃えで30ドットに設定されます。そして、skipCurrentElement ()を使って要素の残りをスキップする。

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

readFolder() 関数は項目を作成し、フォルダ要素の内容を反復処理し、フォルダ要素の内容を表す子をこの項目に追加します。フォルダーの内容に対するループは、readXBEL() のものと形式が似ていますが、フォルダーのタイトルを設定するために title 要素を受け付けるようになっている点が異なります。

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

createChildItem() ヘルパー関数は、指定されたアイテムの子か、親アイテムがない場合はツリーウィジェットの直接の子である、新しいツリーウィジェットアイテムを作成します。新しいアイテムのUserRole を現在の XML 要素のタグ名に設定し、XbelWriter::writeFile() がUserRole を使用する方法と一致させます。

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

MainWindow クラスの定義

MainWindow クラスはQMainWindow のサブクラスで、File メニューと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;
};

MainWindow クラスの実装

MainWindow コンストラクタは、そのQTreeWidget オブジェクト、treeWidget を、各ブックマークのタイトルと位置のための列見出しを持つ、独自の中心ウィジェットとして設定します。ツリーウィジェット内の個々のブックマークに対してユーザーがアクションを実行できるカスタムメニューを設定します。

createMenus() 、独自のメニューとそれに対応するアクションを設定します。タイトルを設定し、準備ができたことをアナウンスし、利用可能なスクリーンスペースの適切な割合にサイズを設定します。

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

カスタムメニューは、ユーザーがブックマークを右クリックしたときにトリガーされ、ブックマークをリンクとしてコピーしたり、参照するURLを開くようにデスクトップブラウザに指示したりします。このメニューは、onCustomContextMenuRequested() によって実装されています(関連機能が有効な場合)。

#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)

createMenus() 関数は、fileMenuhelpMenu を作成し、QAction オブジェクトを追加し、open()saveAs()about() 関数、QWidget::close ()、QApplication::aboutQt ()とさまざまにバインドする。接続は以下の通りである:

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

これにより、以下のスクリーンショットに示すメニューが作成される:

open() 関数を起動すると、ブックマークファイルを選択するためのファイルダイアログが表示されます。ファイルが選択されると、XBelReader を使って解析され、treeWidget にブックマークが入力されます。ファイルを開いたり解析したりする際に問題が発生した場合は、ファイル名とエラーメッセージを含む適切な警告メッセージがユーザーに表示されます。そうでない場合は、ファイルから読み込まれたブックマークが表示され、ウィンドウのステータスバーにファイルが読み込まれたことが簡単に報告されます。

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

saveAs() 関数はQFileDialog を表示し、ブックマークデータのコピーを保存するfileName をユーザーに求める。open() 関数と同様に、この関数もファイルに書き込めなかった場合に警告メッセージを表示する。

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

about() 関数は、例の簡単な説明、または Qt と使用中のバージョンに関する一般的な情報をQMessageBox に表示します。

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() 関数

main() open() 関数は をインスタンス化し、それを表示するために 関数を呼び出します。MainWindow show()

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

XBELファイルの詳細については、XML Bookmark Exchange Language Resource Pageを参照してください。

プロジェクト例 @ code.qt.io

©2024 The Qt Company Ltd. ここに含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 ここで提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。