SoupProject::MiX::sample::xmlpb

# SoupProject::MiX

## 電話帳 - SAXによる実装

電話帳をSAXを用いて実装してみましょう。
一応別ページでDOMによる実装も示してありますが、このような単純な プログラムではSAXのほうが遥かにシンプルに実装できるでしょう。
(ここを読む前にxml2htmlの項を読んでおいたほうがいいでしょう。)

    #include <MiX/SAX_Parser.h>

    #include <iostream>
    #include <fstream>
    #include <string>
    #include <utility>
    #include <iterator>

まず必要なヘッダをインクルードします。
SAXを使う場合MiX/SAX_Parser.hをincludeすれば、必要なものがすべてinclude されます。

    typedef std::map<std::string,std::string> PhoneBook;
    typedef std::pair<std::string,std::string> Person;

電話帳の要素を表すPerson型と、電話帳を表すPhoneBook型を定義します。
Person型は名前と電話番号を格納するpairで、PhoneBookはPersonのlistです。

    std::string input(const char* prompt){
      std::string ret;
      std::cout << prompt << ": " << std::flush;
      std::cin >> ret;
      return ret;
    }

標準出力にプロンプトを出して標準入力から一行読み取る関数です。

    void outputPerson(const Person& per){
      std::cout << per.first << " : " << per.second << std::endl;
    }

Personを出力する関数です。

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

SAXを使う上で欠かせない、SAXのイベントを受け取るハンドラを定義します。
ここでXMLからPhoneBookオブジェクトを生成しています。

      PhoneBook& pb_;
      Person cur_;
      std::string str_;

PhoneBook生成に必要な変数を定義しています。

- pb_

> 生成先のPhoneBookへの参照です

- cur_

> 生成中のPersonオブジェクトを格納します

- str_

> 取得した文字列を一時格納しておきます

    public:
      EventHandler(PhoneBook& pb) : pb_(pb){
      }

生成するPhoneBookへの参照を渡してEventHandlerを初期化します。

      void onStart(MiX::XMLString<char> name,MiX::AttrMap<char> attr){
        str_="";
      }

タグが開始した時呼び出されるハンドラです。
ここではstr_を初期化しているだけです。

      void onText(MiX::XMLString<char> text){
        str_+=text;
      }

XML内の文字列を検出した時に呼び出されるハンドラです。
str_に文字列を加えています。

      void onEnd(MiX::XMLString<char> name){
        if(name=="Person") pb_.insert(cur_);
        else if(name=="Name") cur_.first = str_;
        else if(name=="Phone") cur_.second = str_;
      }

タグが終了した時に呼び出されるハンドラです。
ここで主な処理を行っています。
終ったタグがNameタグだったらcur_のfirstにstr_を格納。
終ったタグがPhoneタグならcur_のsecondにstr_を格納。
Personタグならばcur_をpb_に追加します。
このようにしてpb_を完成させます。

    };

    void storeFile(const char* fn,const PhoneBook& pb){
      std::ofstream fout(fn);
      fout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl
           << "<PhoneBook>";
      PhoneBook::const_iterator it = pb.begin();
      PhoneBook::const_iterator last = pb.end();
      for( ;it!=last;++it){
        fout << "\t<Person>" << std::endl
    	 << "\t\t<Name>" << it->first << "</Name>" << std::endl
    	 << "\t\t<Phone>" << it->second << "</Phone>" << std::endl
    	 << "\t</Person>" << std::endl;
      }
      fout << "</PhoneBook>" << std::endl;
      fout.close();
    }

PhoneBookをXML文字列にしてファイルに出力する関数です。
SAXの場合、XMLの解析はできますが、XMLの出力はできないので、このように 自分で出力プログラムを書いてやる必要があります。

    int main(){
      PhoneBook pb;
      std::ifstream fin("phonebook.xml");
      if(fin.is_open()) {
        EventHandler handler(pb);
        MiX::SAX_Parser<char> parser;
        parser.setEventHandler(&handler);
        parser.setIgnoreSpace(true);
        parser.parse(fin);
      }

ファイルがあれば、ファイルをパースして、PhoneBookオブジェクトを生成します。
ファイルがなければ、空のPhoneBookオブジェクトのまま処理します。

      char cmd = "\0";
      while(cmd!="q"){
        std::string s = input("add/delete/find/list/clear/quit [a,d,f,l,c,q]");
        cmd = s.at(0);

'q'コマンドが入力されるまでコマンドループを繰り返します

        switch(cmd){
        case "a" : case "A" : {
          std::string name = input("Name");
          if(pb.find(name) != pb.end())
    	std::cerr << "error: already exists." << std::endl;
          else {
    	std::string phone = input("Phone");
    	pb.insert(Person(name,phone));
          }
          break;
        }

'a'が入力された場合、PhoneBookに新たなPersonを追加します。
名前を受け取って、その名前を持つPersonが既に登録されていた場合は エラーを出します。
存在しなければ電話番号も受け取ってPersonオブジェクトを生成、pbに追加します。

        case "d" : case "D" : {
          std::string name = input("Name");
          PhoneBook::iterator it =  pb.find(name);
          if(it==pb.end())
    	std::cerr << "error: not found." << std::endl;
          else pb.erase(it);
          break;
        }

'd'が入力された場合、入力された名前のPersonを削除します。
名前を受け取って、pbからその名前を持ったオブジェクトを検索し、 見つからなかった場合はエラーを出し、見つかった場合はそのPersonを削除します。

        case "f" : case "F" : {
          std::string name = input("Name");
          PhoneBook::iterator it = pb.find(name);
          if(it==pb.end())
    	std::cerr << "error: not found." << std::endl;
          else outputPerson(*it);
          break;
        }

'f'が入力された時、名前からPersonを検索して、 そのPersonを表示します。
名前を受け取って、pbからその名前を持ったオブジェクトを検索し、 見つからなかった場合はエラーを出し、見つかった場合はそのPersonを出力します。

        case "l" : case "L" : {
          std::cout << pb.size() << "entries:" << std::endl;
          std::for_each(pb.begin(),pb.end(),outputPerson);
          break;
        }

'l'が入力された時は、全Personを表示します。
ostream_iteratorを使って全Personを出力しています。

        case "c" : case "C" : {
          pb.clear();
          break;
        }

'c'が入力された時は全Personを削除します。

        // 終了
        case "q" : case "Q" :
        default :
          break;
        }
      }
      storeFile("phonebook.xml",pb);

終了する前にpbをファイルに出力します。

    }