2012年2月9日木曜日

( Qt C++ )XMLの読み込み(DOM編)


はい、それでは表題の件やっていきます。

今回はDOMを使用したXMLをやっていきます。
DOMとはDocument Object Modelの略で、W3Cから勧告されている HTML文書やXML文書をアプリケーションから利用するためのAPIです。XMLデータをツリーとして扱えるメリットがありますが、XMLを全て読み込んでからの扱いを前提とするため動作速度が遅かったり、メモリーの使用量が大きくなるという欠点があるそうです。

サンプルはC++ GUI Programming with Qt4 345ページのものをほぼそのまま使用します。

そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)

なお、.proファイルに Qt += xml の記述をしておいてください。でなければQXml関連をコードに含められません。

ではコードを


(読み込むXML)
<?xml version="1.0"?>
<bookindex>
  <entry term="sidebearings">
    <page>10</page>
    <page>34-35</page>
    <page>307-308</page>
  </entry>
  <entry term="subtraction">
    <entry term="of pictures">
      <page>115</page>
      <page>244</page>
    </entry>
    <entry term="of vectors">
      <page>9</page>
    </entry>
  </entry>
</bookindex>


(domparser.h)
#include <qxml.h>
#include <qdom.h>
#include <QTreeWidget>
#include <QMessageBox>
class DomParser
{
public:
    DomParser(QIODevice *device, QTreeWidget *tree);
private:
    void parseEntry(const QDomElement &element,
                    QTreeWidgetItem *parent);

    QTreeWidget *treeWidget;
};


(domparser.cpp)
#include "domparser.h"

DomParser::DomParser(QIODevice *device, QTreeWidget *tree)
{
    treeWidget = tree;
    QString errorStr;
    int errorLine;
    int errorColumn;
    QDomDocument doc;

    if (!doc.setContent(device, true, &errorStr, &errorLine,
                        &errorColumn))
    {
        QMessageBox::warning(0, QObject::tr("DOM Parser"),
                             QObject::tr("Parse error at line %1, "
                                         "column %2:\n%3")
                             .arg(errorLine)
                             .arg(errorColumn)
                             .arg(errorStr));
        return;
    }

    QDomElement root = doc.documentElement();

    if (root.tagName() != "bookindex")
        return;

    QDomNode node = root.firstChild();

    while (!node.isNull())
    {
        if (node.toElement().tagName() == "entry")
            parseEntry(node.toElement(), 0);

        node = node.nextSibling();
    }
}

void DomParser::parseEntry(const QDomElement &element,
                           QTreeWidgetItem *parent)
{
    QTreeWidgetItem *item;

    if (parent)
    {
        item = new QTreeWidgetItem(parent);
    } else
    {
        item = new QTreeWidgetItem(treeWidget);
    }

    item->setText(0, element.attribute("term"));
    QDomNode node = element.firstChild();

    while (!node.isNull())
    {
        if (node.toElement().tagName() == "entry")
        {
            parseEntry(node.toElement(), item);
        }
        else if (node.toElement().tagName() == "page")
        {
            QDomNode childNode = node.firstChild();

            while (!childNode.isNull())
            {
                if (childNode.nodeType() == QDomNode::TextNode)
                {
                    QString page = childNode.toText().data();
                    QString allPages = item->text(1);

                    if (!allPages.isEmpty())
                        allPages += ", ";

                    allPages += page;
                    item->setText(1, allPages);
                    break;
                }

                childNode = childNode.nextSibling();
            }
        }
        node = node.nextSibling();
    }
}

はい簡単ですね。SAXの時よりコードはシンプルになっています。
ヘッダ部はいいですね。そのまんまです。

cppは説明します。
まずはコンストラクタです。
まずDomDocument::setContentでデバイスを指定しています。もし失敗したらメッセージボックスで表示するようにしています。
次に最初のDomElementをdocumentElement()で取り出し、"bookindex"であるか確認しています。偽なら失敗としてreturn。正しいなら処理を進めます。
最後に格納されているノードを取り出し、ループに入り、"entry"ならparseEntry()を呼び出してパースしています。

次はparseEntry()です。
まず引数のQTreeWidgetItemが親なのかどうかチェックしそれぞれセットします。
次に要素を全て取り出し、ループに入ります。要素が"entry"ならばparseEntry()を再帰的に呼び出します。要素が"page"ならば更にループに入りTextNodeを探します。見つかればQDomNodeQDomTextに変換しQStringを取り出します。次にitemの1列目にすでにtextがセットされているか確認し、セットされていればコンマで区切ってからitemに新しいtextとしてallPagesをセットしています。

実行コードは以下のようになります。


(mainwindow.cpp)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "domparser.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QStringList labels;
    labels << QObject::tr("Terms") << QObject::tr("Pages");

    ui->treeWidget->setHeaderLabels(labels);
    ui->treeWidget->setWindowTitle(QObject::tr("DOM Parser"));
    ui->treeWidget->show();

    QFile file("C:\\Users\\windows7\\Desktop\\test.txt");
    DomParser(&file, ui->treeWidget);
}

はい簡単ですね。見てのとおりです。


実行すると以下のようになります。
以上です。

※補足 C++ GUI Programming with Qt4 345pageより
 

(ほとんどの方はわかっているとは思いますがNokiaのQtの公式ページよりC++ GUI Programming with Qt4 1st PDF 英語版が無料でダウンロードできます。そんなに英語も難しくなく、サンプルプログラムも豊富で実践的でわかりやすいです。((とはいえ先にQtをはじめようをやっとかないと厳しいですが...))まだ見ていない方はこちらも参考にしてみてください。)