SoupProject::MiX::Making of xml2html

xml2htmlについて

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

どのように実装するか。

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

イベントハンドラの実装

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

Char
使用する文字クラス
Traits
Charの特性を表すクラス(詳細は、std::basic_stringを参照)
XMLTraits
CharのXML文書内における特性を表すクラス

です。
が、TraitsとXMLTraitsはデフォルトで定義されているので、 char型やwchar_t型の文字列を使ってパージングする分には指定する必要がありません。
ので、イベントハンドラの宣言はこのようになります。

#include <MiX.h>

#include <iostream>
#include <fstream>

using namespace std;
using namespace MiX;


class EventHandler : public SAX_EventHandler<char>{

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


  int m_iIndent;
  ostream& m_out;
public:
  EventHandler(ostream& out) : m_out(out){
    m_iIndent = 0;
  }

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

 
  virtual void onXMLDeclaration(AttrMap<char> attr){
    m_out << "<html>" << endl
	  << "<head><title>Result of xml2html</title></head>" 
	  << endl << "<body>" << endl;
  };

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

  virtual void onStart(XMLString<char> sName,AttrMap<char> attr){
    m_out << "<span style=\"margin-left:" << 40*m_iIndent 
	  << "px\">" << "<b>&lt;" << sName << "</b>";
    AttrMap<char>::iterator it = attr.begin();
    for( ;it!=attr.end();it++){
      m_out << " " << it->first 
	    << " = <span style=\"color: #000055;\">\'" 
	    << it->second << "\'</span>" << endl;
    }
    m_out << "<b>&gt;</b>" << flush;
    m_out << "</span><br>" << endl;;
    ++m_iIndent;
  };

onStartの引数の意味は、

sName
タグの名前
attr
タグの持つアトリビュートを格納したstd::map

です。
onStartでは、m_iIndentが示すインデント量に従ってタグ名を出力したあと、attrのイテレータを使って、attr内の全ての要素を出力しています。
そして、その後、メンバ変数m_iIndentをインクリメントしています。
次にonEndを記述します。

  virtual void onEnd(XMLString<char> sName){
    m_iIndent--;
    m_out << "<span style=\"margin-left:" << 40*m_iIndent 
	  << "px\">" << "<b>&lt;/" << sName << "&gt;</b>" 
	  << "</span><br>";
    if(m_iIndent==0){
      m_out << "</body></html>" << endl;
    }
  };

ここでは、m_iIndentをデクリメントして、終了タグを出力しています。
また、m_iIndentが0ならパージングが終ったとみなし、HTMLのフッタを出力しています。
では、次にonTextとonCommentを同時にみてみましょう。

  virtual void onText(XMLString<char> sText){
    m_out << "<span style=\"margin-left:" << 40*m_iIndent 
	  << "px\">" << sText << "</span><br>";
  };
  virtual void onComment(XMLString<char> sText){
    m_out << "<span style=\"margin-left:" << 40*m_iIndent 
	  << "px\">" << "<span style=\"color: #777777;\">" 
	  << "&lt;!-" << sText << "--&gt;" 
	  << "</span>" << "</span><br>";
  };
};

onTextとonCommentはm_iIndentの量に従って、データを出力しているだけです。

};

これでイベント駆動部分の実装は終りました。 これだけで、ほとんどの部分は完成しています。
あとは、パージングを開始するまでの処理をmainに書いておわりです。

mainの実装

最後にわずかなmainの実装です。

int main(int argc,char* argv[]){

まず、標準入力から文字列を読み込みます。
XMLString<char>はstringやbasic_string<char>とほぼ等価なので、そっちを使っても問題ありません。

  XMLString<char> str;
  char c;
  cin.read(&c,sizeof(char));
  while(!cin.eof()){
    str += c;
    cin.read(&c,sizeof(char));
  }

パージングに失敗した時、MiXは例外を投げるので、tryブロックを用意します。

  try{

パーサと、先程記述したイベントハンドラを初期化します。

    SAX_Parser<char> parser;
    EventHandler handler(std::cout);

パーサを設定します。
ここでは空白文字列を無視する設定にしています。

    parser.setIgnoreSpace(true);

次に、ハンドラとパーサを結びつけます。

    parser.setEventHandler(&handler);

最後にパージングを開始します。

    parser.parse(str.c_str());

    return 0;

パージング中にエラーが起きた時の例外をキャッチするブロックを記述します。

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

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

完成

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