SoupProject::MiX::Making of csv2xml

csv2xmlについて

csv2xmlはcsv形式のファイルを読み込んで、 xml(というかhtml)形式に変換するソフトです。

A1,B1,C3
A2,B2,C3
A3,B3,C3
というようなCSVを、
<?xml?>
<table>
    <tr>
        <td>
	    A1
	</td>
	<td>
	    B1
	</td>
	<td>
	    C1
	</td>
    </tr>
    <tr>
        <td>
	    A2
	</td>
	<td>
	    B2
	</td>
	<td>
	    C2
	</td>
    </tr>
    <tr>
        <td>
	    A3
	</td>
	<td>
	    B3
	</td>
	<td>
	    C3
	</td>
    </tr>
</table>
というようなxmlに変換します。

DOMとは何か?

この例も先のxml2htmlのように、XMLパーサを持ち出さなくても解決できそうですが、MiXを使います。(MiXのドキュメントですんで。)
XMLドキュメントを構築するだけなので、今回はパーサを用いません。
MiXのDOMで使われているクラス郡によって解決します。

DOMとは何か?

DOMはXMLドキュメントを構成する一つ一つの要素をオブジェクトの木構造として表現し操作するための方式です。
例えば上のxmlドキュメントはDOMでは下の木になります。

Document
   +-Element(Table)
         +-Element(Tr)
	 |    +-Element(Td)
	 |    |    +-Text(A1)
	 |    |
	 |    +-Element(Td)
	 |    |    +-Text(B1)
	 |    |
	 |    +-Element(Td)
	 |         +-Text(C1)
	 |
         +-Element(Tr)
	 |    +-Element(Td)
	 |    |    +-Text(A2)
	 |    |
	 |    +-Element(Td)
	 |    |    +-Text(B2)
	 |    |
	 |    +-Element(Td)
	 |         +-Text(C2)
	 |
         +-Element(Tr)
	      +-Element(Td)
	      |    +-Text(A4)
	      |
	      +-Element(Td)
	      |    +-Text(B3)
	      |
	      +-Element(Td)
	           +-Text(C3)
DOMを用いてXML文書を構築するときは、こういう木を作ってやればいいのです。
では実装を追いかけましょう

実装その1 - csvをパージングする

まずcsvをパージングする関数を作りましょう。
一行入力すると、stringのlistを返します。

#include <iostream>
#include <MiX.h>

using namespace std;
using namespace MiX;

list<string> split(string& s,char c){
  /* sをcで分ける */
  list<string> ret;
  string::iterator it = s.begin();
  string::iterator itCur = it;
  string::iterator itEnd = s.end();
  while(itCur!=itEnd){
    if(*itCur==c){
      ret.push_back(string(it,itCur));
      it=itCur;
      ++it;
    }
    ++itCur;
  }
  ret.push_back(string(it,itCur));
  return ret;
}
特に問題はないでしょう。

実装その2 - xml文書を構築する

上はどうでもよくて、このドキュメントはxml文書を構築する方法を伝えるのが目的です。
一番最初に書いたようにDOMでは木構造を作ることによってxmlを表現します。
なので、その木の根本にあたるDocumentから作りましょう。

  Document<char>& doc = Document<char>::create("table");

こんな感じで最初の根本ができます。
引数の意味は、Documentと同時に生成される、一番根本にあるElementの名前です。
なお、MiXのDOMでは基本的に参照でオブジェクトをやりとりします。
ちょっと不自由かもしれませんが安全ですので慣れてください。
また、どうしても参照を使えない場合は、ポインタを用いてください。値コピーはできません。(できる場合もありますが。)

あとは、一行ずつ標準入力から読みとって木を構築しましょう。

  while(!cin.eof()){
    string s;
    getline(cin,s);
    Element<char>& elTr=Element<char>::create("tr",doc.getRoot());
    list<string> l=split(s,',');
    list<string>::iterator it = l.begin();
    list<string>::iterator itEnd = l.end();
    for( ;it!=itEnd;it++){
      //cerr << '.' << flush;
      Element<char>& elTd=Element<char>::create("td",elTr);
      Text<char>::create(it->c_str(),elTd);
    }
  }

標準C++ライブラリに含まれるgetline関数でcinから一行取得し、 "tr"という名前のElementを作っています。
Element<>::createの引数は、名前と親Elementです。(MiXでは親のないオブジェクトは作れないようになっています。)
そして、先程のcsvをパージングする関数に取得した文字列を与え、 戻り値をイテレータを使用して全て処理します。
処理の内容は、先程作った"tr"というエレメントを親にした"td"というエレメントを作り、その"td"を親にしたcsvの該当部分の文字列を持ったテキスト・オブジェクトを生成しています。
これを全ての行に対して繰り返せば木は完成します。

実装その3 - 出力

では完成した木をXML文字列にして標準出力に流しましょう。
これは一行でOKです。

  cout << doc.toString(true) << endl;

DocumentオブジェクトのtoStringメソッドを呼べばOKです。
toStringの引数はbool値でインデントをするか、しないかを決定します。
デフォルトでfalseなので、インデントをする場合はtoString(true)と、しない場合はtoString(false)またはtoString()で呼び出すことができます。
これで書き込みは終りです。
最後にドキュメントの後始末をしましょう。

  doc.destroy();
  return 0;
}

destroy関数は全てのDOMオブジェクトに装備されたメソッドで、 自分自身を破棄します。親があるオブジェクト(つまりDocument以外の全てのオブジェクト)の場合、親に自分が破棄されたことを伝え、親から参照できないようにします。
子を持つオブジェクトの場合、全ての子も破棄します。

完成

以上でcsv2xmlの実装は終了です。
XMLパーサのドキュメントなのに、肝心のパーサが登場していませんが、 DOM操作についての実装方法について示してみました。
DOMはこういったXMLの操作に強いです。