2012年2月14日火曜日

( Qt C++ )XMLの書き込み(Trolltechで紹介されたクラスを使用)


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

C++ GUI Programming with Qt4 348ページにはDOMを使った書き込みのほかにもう一つ方法があると書かれています。

We can generate XML by hand.
(テメェで書け)

だそうです。サンプルもQTextStreamを使ってそれはもうそのまんまXML書いていっているので、なんていうか...見るからにそのまんまなんでここで取り上げるのはやめます。(意味がないので)

なので以上です...というわけではありません。
C++ GUI Programming with Qt4にはさらに
http://doc.trolltech.com/qq/qq05-generating-xml.htmlにXML書き出しのQt3用のシンプルなクラス公開されていると書かれているので、それを今回はQt4用に変換して書いていこうと思います。

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

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

ではコードを

(xmlwriter.h)
#include <qmap.h>
#include <qstring.h>
#include <qtextstream.h>
#include <qtextcodec.h>

class AttrMap : public QMap<QString, QString>
{
public:
    AttrMap() { }
    AttrMap( const QString& name, const QString& value ) {
    insert( name, value );
    }
};

class XmlWriter
{
public:
    XmlWriter( QIODevice *device, QTextCodec *codec = 0 );
    ~XmlWriter();

    void writeRaw( const QString& xml );
    void writeString( const QString& string );
    void writeOpenTag( const QString& name, const AttrMap& attrs = AttrMap() );
    void writeCloseTag( const QString& name );
    void writeAtomTag( const QString& name, const AttrMap& attrs = AttrMap() );
    void writeTaggedString( const QString& name, const QString& string,
                const AttrMap& attrs = AttrMap() );
    void newLine();
    void setIndentSize( int size ) { indentSize = size; }
    void setAutoNewLine( bool on ) { autoNewLine = on; }

private:
    QString protect( const QString& string );
    QString opening( const QString& tag, const AttrMap& attrs = AttrMap() );
    void writePendingIndent();

    QTextStream out;
    QString indentStr;
    int indentSize;
    bool autoNewLine;
    bool atBeginningOfLine;
};

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

XmlWriter::XmlWriter( QIODevice *device, QTextCodec *codec )
    : indentSize( 4 ), autoNewLine( false ), atBeginningOfLine( true )
{
    out.setDevice( device );
    if ( codec == 0 ) {
        out.setCodec(QTextCodec::codecForName("UTF-8"));
    } else {
    out.setCodec( codec );
    out << "<?xml version=\"1.0\" encoding=\""
        << protect( codec->name() ) << "\"?>\n";
    }
}

XmlWriter::~XmlWriter()
{
    if ( autoNewLine && !atBeginningOfLine )
    out << endl;
}

QString XmlWriter::protect( const QString& string )
{
    QString s = string;
    s.replace( "&", "&" );
    s.replace( ">", ">" );
    s.replace( "<", "<" );
    s.replace( "\"", """ );
    s.replace( "\'", "'" );
    return s;
}

QString XmlWriter::opening( const QString& tag, const AttrMap& attrs )
{
    QString s = "<" + tag;
    AttrMap::ConstIterator a = attrs.begin();
    while ( a != attrs.end() ) {
    s += " " + a.key() + "=\"" + protect( *a ) + "\"";
    ++a;
    }
    s += ">";
    return s;
}

void XmlWriter::writePendingIndent()
{
    if ( atBeginningOfLine ) {
    out << indentStr;
    atBeginningOfLine = false;
    }
}

void XmlWriter::newLine()
{
    out << endl;
    atBeginningOfLine = true;
}

void XmlWriter::writeRaw( const QString& xml )
{
    out << xml;
    atBeginningOfLine = false;
}

void XmlWriter::writeString( const QString& string )
{
    out << protect( string );
    atBeginningOfLine = false;
}

void XmlWriter::writeOpenTag( const QString& name, const AttrMap& attrs )
{
    writePendingIndent();
    out << opening( name, attrs );
    indentStr += QString().fill( ' ', indentSize );
    if ( autoNewLine )
    newLine();
}

void XmlWriter::writeCloseTag( const QString& name )
{
    indentStr = indentStr.mid( indentSize );
    writePendingIndent();
    out << opening( "/" + name );
    if ( autoNewLine )
    newLine();
}

void XmlWriter::writeAtomTag( const QString& name, const AttrMap& attrs )
{
    writePendingIndent();
    QString atom = opening( name, attrs );
    atom.insert( atom.length() - 1, "/" );
    out << atom;
    if ( autoNewLine )
    newLine();
}

void XmlWriter::writeTaggedString( const QString& name, const QString& string,
                   const AttrMap& attrs )
{
    writePendingIndent();
    out << opening( name, attrs );
    writeString( string );
    out << opening( "/" + name );
    if ( autoNewLine )
    newLine();
}
はいややこしいですね。めんどくさいのでサラッとしか説明しません。内部で何やってるか詳細に知りたい方は自分でヘッダ、cppを作成してこのコードをコピーしてそれぞれ動作を追ってください。

基本UTF-8、第2引数が指定された場合のみそのコーデックとなります。このクラスを使えば変換必須の文字(<とか)などを自動で変換し、またインデントなども処理してくれるので、全部自分で書くよりは楽ですね。


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

(mainwindow.cpp)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "xmlwriter.h"
#include <QTextCodec>

void MainWindow::writeProperty( XmlWriter& xw, const QString& name, const QString& type,
            const QString& value )
{
    xw.writeOpenTag( "property", AttrMap("name", name) );
    xw.writeTaggedString( type, value );
    xw.writeCloseTag( "property" );
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QFile file("C:\\Users\\windows7\\Desktop\\test.txt");

    if (file.open(QFile::WriteOnly| QFile::Truncate))
    {
            QTextCodec::setCodecForTr( QTextCodec::codecForName("Shift-JIS"));

            XmlWriter xw( &file,  QTextCodec::codecForName("Shift-JIS"));
            xw.setAutoNewLine( true );
            xw.writeRaw( "<!DOCTYPE UI><UI version=\"3.1\">" );
            xw.newLine();
            xw.writeTaggedString( "class", tr("サンプル1") );

            xw.writeOpenTag( tr("サンプルクラス"), AttrMap("class",tr( "サンプル1")) );

            writeProperty( xw, "name", "string", tr("名前") );
            writeProperty( xw, "caption", "string", tr("キャプション") );

            xw.writeOpenTag( "element1-2" );

            writeProperty( xw, "name", "string", "ElementName" );
            writeProperty( xw, "text", "string", "Rock && Roll" );

            xw.writeCloseTag( "element1-2"  );
            xw.writeCloseTag(  tr("サンプルクラス") );

            AttrMap attrs;
            attrs.insert( "spacing", "6" );
            attrs.insert( "margin", "11" );
            xw.writeAtomTag( "layoutdefaults", attrs );
            xw.writeRaw( "</UI>" );
    }
    file.close();
}

MainWindow::~MainWindow()
{
    delete ui;
}
はい微妙ですね。まぁXMLを全て自分で書くよりはマシになってますが、手間はDOMとあまり変わらないような感じがします。

これを実行して書き出されるXMLは以下の通りです。

(書き出されるXML)
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE UI><UI version="3.1">
<class>サンプル1</class>
<サンプルクラス class="サンプル1">
    <property name="name">
        <string>名前</string>
    </property>
    <property name="caption">
        <string>キャプション</string>
    </property>
    <element1-2>
        <property name="name">
            <string>ElementName</string>
        </property>
        <property name="text">
            <string>Rock && Roll</string>
        </property>
    </element1-2>
</サンプルクラス>
<layoutdefaults margin="11" spacing="6"/>
</UI>
以上です。