SoupProject::MiX

電話帳 - DOMによる実装

電話帳をDOMを用いて実装してみましょう。
電話帳のようなリスト状のデータを扱う場合はSAXのほうが楽だけど、 DOMによる実装の一例として、書いて見ます。

#include <MiX/MiX.h>

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

まず必要なヘッダをインクルードします。
MiX/MiX.hはMiXの全ファイルをincludeします。コンパイルが遅くなりますが、 DOMを使用する場合、イチイチincludeするのは多少面倒なので、 このようにしても良いと思います。

typedef MiX::Element<char> Person;
typedef std::map<std::string,Person*> Index;
 

電話帳の要素を表すPerson型と、電話帳の索引を表すIndex型を定義します。

class PhoneBook {
  MiX::Document<char>& doc_;
  Index index_;
  MiX::Document<char>& loadDocument(std::string fname){
    std::ifstream fin(fname.c_str());
    if(!fin){
      MiX::Document<char>& ret = MiX::Document<char>::create("PhoneBook");
      MiX::Attribute<char>::create("version","1.0",ret);
      MiX::Attribute<char>::create("encoding","UTF-8",ret);
      return ret;
    }else{
      MiX::DOM_Parser<char> parser;
      parser.setIgnoreSpace(true);
      return parser.parse(fin);
    }
  }
 

loadDocumentメソッドはファイル名を与えると ファイルを読み込んでDOMパーサにかけて、DOMのDocumentオブジェクトを返します。 もしファイルが存在しない場合、 空のドキュメントを作ってそれを返します。
DOMParser<...>::setIgnoreSpace(bool)はXML文書中に現われるスペースやタブ、 改行を無視するかどうかを設定するメソッドです。
単なる文書整形のためにタブや改行が使われている場合、これを設定するとで パージング時にそれらの空白を無視してくれるため処理がとても簡単になります。 ただし、空白が意味を持つ文書をパージングする場合は使ってはいけません。
これは先述のdoc_を初期化するためのメソッドです。

  Index loadIndex(MiX::Document<char>& doc){
    Index ret;
    MiX::NodeList<char>::iterator it = doc.getRoot().getChildren().begin();
    MiX::NodeList<char>::iterator last = doc.getRoot().getChildren().end();
    for( ;it!=last;++it){
      if((*it)->getType()==MiX::Node_Element){
	Person* p = dynamic_cast<Person*>(*it);
	ret.insert(std::make_pair((*p)("Name").getText(),p));
	std::cout << (*p)("Name").getText() << " Loaded." << std::endl;
      }
    }
    return ret;
  }
 

loadIndexメソッドはDocumentオブジェクトを与えると、 そこから索引を生成してくれます。
これを使ってindex_を初期化します。
doc.getRoot()でドキュメントのルートノードを得て、 そのノードにgetChildrenで子ノードのリストを得ています。
そして、イテレータを用いて全ての子Elementに対して、 名前とそのElement自身へのポインタをindex_に登録しています。 これで索引のできあがりです。

public:
  PhoneBook(std::string fname)
    : doc_(loadDocument(fname)),index_(loadIndex(doc_)){ }
 

コンストラクタです。
ファイル名を元にloadDocumentおよびloadIndexを呼んで 自分自身を生成します。

  void store(std::string fname){
    std::ofstream fout(fname.c_str());
    fout << doc_.toString(true) << std::endl;
    fout.close();
  }
 

storeメソッドはファイルに自分自身を保存するためのメソッドです。
DOMオブジェクトをそのままいじくってるので、 格納はそのDOMオブジェクトを文字列化して書き込むだけです。 toStringの引数はインデントを行うか否かです。

  void addPerson(std::string name,std::string phone){
    MiX::Element<char>& e=MiX::Element<char>::create("Person",doc_.getRoot());
    MiX::Element<char>& n=MiX::Element<char>::create("Name",e);
    MiX::Element<char>& p=MiX::Element<char>::create("Phone",e);
    n.setText(name);
    p.setText(phone);
    index_.insert(std::make_pair(name,&e));
  }
 

addPersonは電話帳に新たな人を追加するためのメソッドです。
doc_に新しい人を表すエレメントを追加した後、 索引index_に追加したデータを登録しています。

  Index& getIndex(){
    return index_;
  }
};
 

getIndexはindex_のgetterです。
以上でPhoneBookクラスは終りです。
あとはこまごまとした部分を作っていきます。

void outputPerson(std::ostream& os,Person& p){
  os << p("Name").getText() << " : " << p("Phone").getText() << std::endl;
}
 

outputPersonはPersonを出力ストリームに出力する関数です。

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

inputは指定された文字列を表示し、ユーザからの入力を受け取る関数です。
BASICのINPUT文と一緒です。

最後にmain関数です。

int main(){
  PhoneBook pb("phonebook.xml");

  char cmd = "\0";
  while(cmd!="q"){
    cmd = input("add/delete/find/list/clear/quit [a,d,f,l,c,q]").at(0);
    switch(cmd){
    case "a" : case "A" : {
      std::string name = input("Name");
      Index::iterator it = pb.getIndex().find(name);
      if(it!=pb.getIndex().end())
	std::cerr << "error: already exists." << std::endl;
      else {
	std::string phone = input("Phone");
	pb.addPerson(name,phone);
      }
      break;
    }
 

'a'が入力されたら、Indexを用い入力された名前が既に存在しないことを確認して、 addPersonで追加します。

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

'd'が入力されたら、Indexを用い入力された名前に相当するエレメントを得て、 destroyメソッドで消去します。

    case "f" : case "F" : {
      std::string name = input("Name");
      Index::iterator it=pb.getIndex().find(name);
      if(it!=pb.getIndex().end())
	outputPerson(std::cout,*(it->second));
      else std::cerr << "error: not found." << std::endl;
      break;
    }
 

'f'が入力されたら、Indexを用い入力された名前に相当するエレメントを得て、 outputPersonで出力します。

    case "l" : case "L" : {
      std::cout << pb.getIndex().size() <<"entries:" << std::endl;
      Index::iterator it = pb.getIndex().begin();
      Index::iterator last = pb.getIndex().end();
      for( ;it!=last;++it) outputPerson(std::cout,*(it->second));
      break;
    }
 

'l'が入力されたら、Indexを順番に走査し、すべてのエレメントを outputPersonで出力します。

    case "c" : case "C" : {
      while( !pb.getIndex().empty() ){
	Index::iterator it = pb.getIndex().begin();
	pb.getIndex().erase(it);
	it->second->destroy();
      }
      break;
    }
 

'c'が入力されたら、Indexを順番に走査し、すべてのエレメントを destroyで消去し、Indexからも消去していきます。

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

'q'が入力されたら、終了します 終了前にデータをファイルに保存します。