SoupProject::MiX::Making of xml2html

xml2htmlについて

xml2htmlとは、xmlファイルをインデントとをつけHTMLファイルに変換する プログラムで、samples/xml2html/にあります。
標準入力から読み込んで標準出力に結果を表示するので、
./xml2html < [入力XMLファイル] > [出力HTMLファイル]
のようにして使います。

どのように実装するか。

これくらい簡単な操作だとXMLパーサを使わずとも、 正規表現置換なんかでできそうな気もしますが、MiXを使いましょう。
この例のようにドキュメントを上から順番に見て行く場合はDOMサブセットより、 SAXのほうが適しています。

イベントハンドラの実装

SAXはイベント駆動型のAPIなので、使用するにはイベントを処理する、 イベントハンドラを記述する必要があります。
またイベントハンドラは、
MiX::SAX_EventHandler<charT,traitsT>
を継承する必要があります。
SAX_EventHandlerの型パラメータの意味は、

charT
使用する文字の型
traitsT
MiXの動作を指定するポリシー

です。
traitsTは特に変わったことをしない限り、デフォルトで定義されているものを使え ば問題ありません。 ので、イベントハンドラの宣言はこのようになります。

#include <iostream>

#include <MiX/MiX.h>

class EventHandler : public MiX::SAX_EventHandler<char>{

コンストラクタおよびメンバ変数は次のようになります。


  int indent_;
  std::ostream& out_;
public:
  EventHandler(std::ostream& out) : out_(out){
    indent_ = 0;
  }

コンストラクタでは出力先のストリームへの参照をout_を受け取って、 indent_というインデント量を示すメンバ変数を0にセットしています。
では各イベントの処理を記述しましょう。
まず、XML宣言(<?xml .....?>のようなもの)を見つけた時に呼ばれる onXMLDeclaration(AttrMap<char> attr)からです。

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

特になにもせずにout_にHTMLヘッダを出力します。
次に、タグが開始した時に呼び出されるonStartを記述します。

  virtual void onStart(MiX::XMLString<char> name,MiX::AttrMap<char> attr){
    out_ << "<div style=\"margin-left:" << 40*indent_
	 << "px\">" << "<b>&lt;" << 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>&gt;</b>" << std::flush;
    out_ << "</div>" << std::endl;
    ++indent_;
  }

onStartの引数の意味は、

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

です。
attrはキーと値のペアを格納する辞書ですが、std::mapと違い順序を保存します。
onStartでは、indent_が示すインデント量に従ってタグ名を出力したあと、attrのイテレータを使って、attr内の全ての要素を出力しています。
そして、その後、メンバ変数indent_をインクリメントしています。

次にonEndを記述します。

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

ここでは、indent_をデクリメントして、終了タグを出力しています。
また、indentが0ならパージングが終ったとみなしHTMLのフッタを出力しています。

では、次にonTextとonCommentを同時にみてみましょう。

  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;\">" 
	 << "&lt;!-" << text << "--&gt;" 
	 << "</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;

SAX_Parserを生成し、setIgnoreSpaceで空白文字を無視することを指定し、
parse(std::cin)で、標準入力からパースさせています。
これで先程作ったイベントハンドラが順番に呼び出されます。

パースが失敗した時、SAX_Parser::parseは例外を投げるので、 catchブロックを書く必要があります。

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

ここでは、エラー文字列を標準エラー出力に出力しているだけです。

完成

以上でxml2htmlの実装は終了です。
このxml2htmlは空タグの終了タグが省略形にならなかったりとしますが、 非常に簡単に実装できました。
このシンプルな実装がSAXの強みです。(Simple API for XMLですんで)