2012年2月6日月曜日

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


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

今回からC++ GUI Programming with Qt4 339ページのXMLの章を順にやっていきます。

今回はSAXを使用したXML読み込みをやっていきます。
まずSAXとはSimple API for XMLの略でランダムアクセスに弱いものの、実行速度が速い、メモリ消費量が少ないなどの利点があります。(SAXの詳細についてはWikiなどで調べてください。)

サンプルはC++ GUI Programming with Qt4 341ページのものをほぼそのまま使用します。
そして、いつものように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>

(saxhandler.h)
#include <qxml.h>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QMessageBox>

class SaxHandler : public QXmlDefaultHandler
{
public:
    explicit SaxHandler(QTreeWidget *tree);//コンストラクタ
    
    //以下オーバライド-----------------------------------
    bool startElement(const QString &namespaceURI,
                      const QString &localName,
                      const QString &qName,
                      const QXmlAttributes &attributes);

    bool endElement(const QString &namespaceURI,
                    const QString &localName,
                    const QString &qName);

    bool characters(const QString &str);
    bool fatalError(const QXmlParseException &exception);
    //---------------------------------------------------
    
private:
    QTreeWidget *treeWidget;
    QTreeWidgetItem *currentItem;
    QString currentText;
};

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

SaxHandler::SaxHandler(QTreeWidget *tree)
{
    treeWidget = tree;
    currentItem = 0;
}

bool SaxHandler::startElement(const QString & /* namespaceURI */,
                              const QString & /* localName */,
                              const QString &qName,
                              const QXmlAttributes &attributes)
{
    if (qName == "entry") {
        if (currentItem) {
            currentItem = new QTreeWidgetItem(currentItem);
        } else {
            currentItem = new QTreeWidgetItem(treeWidget);
        }
        currentItem->setText(0, attributes.value("term"));
    } else if (qName == "page") {
        currentText.clear();
    }
    return true;
}

bool SaxHandler::characters(const QString &str)
{
    currentText += str;
    return true;
}

bool SaxHandler::endElement(const QString & /* namespaceURI */,
                            const QString & /* localName */,
                            const QString &qName)
{
    if (qName == "entry") {
        currentItem = currentItem->parent();
    } else if (qName == "page") {
        if (currentItem) {
            QString allPages = currentItem->text(1);
            if (!allPages.isEmpty())
                allPages += ", ";
            allPages += currentText;
            currentItem->setText(1, allPages);
        }
    }
    return true;
}

bool SaxHandler::fatalError(const QXmlParseException &exception)
{
    QMessageBox::warning(0, QObject::tr("SAX Handler"),
                         QObject::tr("Parse error at line %1, column "
                                     "%2:\n%3.")
                         .arg(exception.lineNumber())
                         .arg(exception.columnNumber())
                         .arg(exception.message()));
    return false;
}


はい簡単ですね。ヘッダ部は見てのとおりです。説明は省略します。

cppは説明します。
まずコンストラクタですがこれは見てのとおり引数のQTreeWidgetを変数treeWidgetに格納し、currentItemが空であることを設定しているだけです。

次はstartElementです。引数の最初の二つは今回使用しないのでコメントアウトしています。まず第3引数のqName(タグ名)が"entry"かを比較し、さらにcurrentItemの実態があるかどうかで子タグであるかを判断してtreeWidgetItemとしてcurrentItemに新規作成or追加していっています。もしqNameが"page"だった場合はcurrentTextを空にしています。

次にcharactersです。
これはXMLのタグの内容を読み出すとき呼び出されるもので、単にcurrentTextに文字を追加しています。これはreader側が自動で呼び出します。

次にendElementです。
これはstartElementと同じ要領の処理です。"page"の場合のときだけ説明します。
まず変数currentItemが空ではないことを確認し、0から数えて1番目の列にセットされているテキストをallPageに取り出します。そしてもし、すでに何らかのテキストがセットされている場合はコンマを追加します。次にallPagesに現在のpageタグの内容が格納されているcurrentTextをセットし、currentItemの1列目にcurrentTextをセットします。
(リファレンス

次はfatalErrorですが見てのとおりです。エラーの際にメッセージが表示されます。


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

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

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)//uiにはGUI部品を記述
{
    ui->setupUi(this);//uiにはGUI部品を初期化
    QStringList labels;
    labels << "Terms" << "Pages";

    ui->treeWidget->setHeaderLabels(labels);
    ui->treeWidget->setWindowTitle("Sax Handler");
    ui->treeWidget->show();

    QFile file("/home/ubuntu001/xmlsample.txt");

    QXmlInputSource inputSource(&file);
    QXmlSimpleReader reader;
    SaxHandler handler(ui->treeWidget);
    reader.setContentHandler(&handler);
    reader.setErrorHandler(&handler);
    reader.parse(inputSource);
}

これも簡単ですね。読み出したいxmlファイルをQXmlInputSouceにセットし、ウィジットがセットされたSaxHandlerをQXmlSimpleReaderへセットしてinputSourceを解析するだけです。


実行すると以下のような感じになります。

以上です。

※補足:今回のSaxHandlerの継承関係図(C++ GUI Programming with Qt4 340pより)