SoupProject::MiX

電話帳 - SAXによる実装

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

/* のんびり楽しみながら書いてたコードを間違えて消してしまったんで、
 * 執念の20分コードでどうぞ。*/
#include <MiX/MiX.h>

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

まず必要なヘッダをインクルードします。

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;
}

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

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

コマンドプロンプトを表示し、コマンド入力を受ける関数です。

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生成に必要な変数を定義しています。

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 error(const char* errmsg){
  std::cout << "Error: " << errmsg << std::endl;
}

エラー文字列を表示するための関数です。

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'){
    cmd = prompt();

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

    switch(cmd){
    case 'a' : case 'A' : {

'a'が入力された場合、PhoneBookに新たなPersonを追加します。

      std::string name = input("Name");
      if(pb.find(name) != pb.end()) error("already exists.");
      else {
	std::string phone = input("Phone");
	pb.insert(Person(name,phone));
      }
      break;
    }

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

    case 'd' : case 'D' : {

'd'が入力された場合、入力された名前のPersonを削除します。

      std::string name = input("Name");
      PhoneBook::iterator it =  pb.find(name);
      if(it==pb.end()) error("not found.");
      else pb.erase(it);
      break;
    }

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

    case 'f' : case 'F' : {

'f'が入力された時、名前からPersonを検索してそのPersonを表示します。

      std::string name = input("Name");
      PhoneBook::iterator it = pb.find(name);
      if(it==pb.end()) error("not found.");
      else outputPerson(*it);
      break;
    }

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

    case 'l' : case 'L' : {

'l'が入力された時は、全Personを表示します。

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

std::for_eachを使って全Personに対しoutputPersonしています。

    case 'c' : case 'C' : {

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

      pb.clear();
      break;
    }

pbに対してclearするだけです。

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

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

}