xml2htmlを作る

xmlファイルを食わせると内容をグラフィカルに表示するHTMLファイルを 吐くプログラムを作ります。
ソースコードはsamples/xml2html/にあります。
この例の様に、ドキュメントを一回走査するだけでOKだったり、 単純なXMLを扱う際にはSAXのほうが適しています。

イベントハンドラの実装

まず、includeするべきものです

#include <iostream>

#include <MiX/SAX_Parser.h>
#include <MiX/SAX_EventHandler.h>
 

iostreamはOKとして、MiX/SAX_ParserはSAXパーサを定義したヘッダで、 SAX_EventHandlerはSAXパーサが通知するイベントを受け取るための ハンドラの基底クラスを定義しています。
SAXではイベントを受け取ることによってXMLを読み込んでいくので、 イベントハンドラを記述します。

class EventHandler : public MiX::SAX_EventHandler<char>{
  int indent_;
  std::ostream& out_;
public:
  EventHandler(std::ostream& out) : out_(out){
    indent_ = 0;
  }
 

SAX_EventHandler(に限らず殆んどのMiXのクラス)はテンプレートクラスで、 文字型と文字型の情報、XMLに関する情報の3つのパラメータを持ちます。
文字型を指定すれば、後はすべてデフォルト値が適用されます。 よほど特別なことをしない限り、デフォルトで十分でしょう。

まず、<? xml ..... ?>を読みとったときに通知されるイベントの ハンドラから記述します。

  virtual void onXMLDeclaration(MiX::AttrMap<char> attr){
    out_ << "<html>" << std::endl
	 << "<head><title>xml2html</title></head>"
	 << std::endl << "<body>" << std::endl;
  }
 

ここではHTMLのヘッダを出力しています。 version属性などを読みとりたい場合は、attrに格納されているので、 attr["version"]などとすることで、取得できます。
AttrMapはstd::mapとほぼ等価なので 詳細はSTLについて調べてください。

次は開始タグを読みとった時に通知されるイベントのハンドラを記述します。

  virtual void onStart(MiX::XMLString<char> name,MiX::AttrMap<char> attr){
    out_ << "<div style=\"margin-left:" << 40*indent_
	 << "px\">" << "<b><" << name << "</b>" << std::endl;
    MiX::AttrMap<char>::iterator it = attr.begin();
    for( ;it!=attr.end();it++){
      out_ << " " << it->first
	   << " = <span style=\"color: #000055;\">\""
	   << it->second << "\"</span>" << std::endl;
    }
    out_ << "<b>></b>" << std::flush;
    out_ << "</div>" << std::endl;
    ++indent_;
  }
 

onStartの引数の意味は

name
タグの名前
attr
タグの持つアトリビュートを格納した辞書

です。
ここでは、名前をと属性をすべて出力した後、indent_を増加させています。

次は、終了タグを読みとった時のハンドラです。

  virtual void onEnd(MiX::XMLString<char> name){
    indent_--;
    out_ << "<div style=\"margin-left:" << 40*indent_
	 << "px\">" << "<b></" << name << "></b>"
	 << "</div>";
    if(indent_==0){
      out_ << "</body></html>" << std::endl;
    }
  }
 

終了タグを読みとった時は、インデントを減らし、タグ名を出力しています。
またindent_が0になったら、XMLのパージングが終了したとみなし、 HTMLのフッタを出力します。

つぎに、XML文中の文字列を読みとった時のハンドラと、 コメントを読みとった時のハンドラを記述します。

  virtual void onText(MiX::XMLString<char> text){
    out_ << "<div style=\"margin-left:" << 40*indent_
	 << "px\">" << text << "</div>" << std::endl;
  }
  virtual void onComment(MiX::XMLString<char> text){
    out_ << "<div style=\"margin-left:" << 40*indent_
	 << "px\">" << "<span style=\"color: #777777;\">"
	 << "<!-" << text << "-->"
	 << "</span>" << "</div>" << std::endl;
  }
};
 

onTextと、onCommentはindent_に従って インデントをしたデータを出力しているだけです。

これでイベントハンドラの実装は終りました。
これでほとんど全て完成しています。
最後にイベントを発行するまでの処理をmainに記述します。

mainの実装

mainでやるべきことはパーサの生成と、パージングの開始だけです。

int main(int argc,char* argv[]){
  try{
    MiX::SAX_Parser<char> parser;
    EventHandler handler(std::cout);
    parser.setIgnoreSpace(true);
    parser.setEventHandler(&handler);
    parser.parse(std::cin);
    return 0;
 

パーサと、先程作ったEventHandlerのインスタンスを生成し、 setEventHandlerで二つを結びつけて、parseしています。
setIgnoreSpaceは、XML文書中の意味がなさそうな空白文字を無視する設定を しています。
「意味がなさそう」ってのはパーサが勝手に判断するので、厳密な処理が必要 になる時は、設定しないほうが良いです。

  }catch(MiX::ParsingException& e){
    std::cerr << e.what() << std::endl;
    return -1;
  }
}

最後に、パージング中にエラーが起こった時のcatchブロックを書きます。
ここでは、エラー文字列を表示するだけです。

もっと見た目の良いHTMLを吐くプログラム(ただし単純な奴)が書けたら、 是非是非MiX-MLに投げてくれると、すごく嬉しいです。