'boost::Tokenizer'에 해당되는 글 1건

  1. 2008/06/18 귀차니스트 strtok 는 이제 그만..

strtok 는 이제 그만..

Programming 2008/06/18 11:58 귀차니스트

  이틀 의 공백기간 후 올리는 포스팅 이군요. 퇴근시간이 약간 늦어지는 것 때문에 막상 도착하니 짧은 시간에 무엇을 할까 고심을 많이 했습니다. 최근 계속해서 알고리즘 트레이닝 북에 있는 문제를 하나씩 풀어가고 있긴 했지만 워낙 남은 시간이 짧아 조금 그렇더군요.

  그래서 오늘 마침 빨리 퇴근한 김에 수정하고 있는 프로그램에서 쓰고 있는 boost::tokenizer 를 보기로 했습니다. 아주 예전 C 부터 사용하던 분들이라면 strtok 함수를 이용하여 토큰을 분리하거나, 직접 코드를 작성하여 코드를 분리하곤 했습니다. 파싱을 통한 토큰 분리를 수행해 주는 여타 클래스도 존재하긴 합니다.
  그런 김에 boost::tokenizer를 보니 stl과 비슷한 방법으로 토큰 분리를 해주는 클래스를 보니 무척 반갑더군요. 그런데 문제는 하나 있었습니다. 일단 코드를 한 번 보도록 하겠습니다.

Tokenizer.cpp (Language : cpp)
  1. #include <iostream>
  2. #include <boost/tokenizer.hpp>
  3. int main( int argc, char **argv )
  4. {
  5.     std::string aa = "a bb ccc ddd";
  6.     boost::tokenizer<> Token(aa);
  7.     for(boost::tokenizer<>::iterator Iter=Token.begin(); Iter!=Token.end();++Iter)
  8.         std::cout << *Iter << "\n";
  9.     return 0;
  10. }

  위 코드 수행시 결과는 a\nbb\rccc\nddd 입니다. \n는 뉴라인이라고 생각하시면 결과를 추측하실 수 있겠죠? 간단하게 출력을 수행하는 부분인데, 기본적인 이 방법으로는 std::string 형인 aa에 한글이 섞여있는 문자열을 적을경우 분리가 불가능하며 심지어 에러를 표시합니다.
  그래서 선언을 봤는데 token_functions.hpp에 있는 seperator를 기본으로 사용하듯이 한글이나 2바이트 확장 아스키에 대해서는 추가로 seperator 클래스를 구현해주어야 하더군요.

sjis_seperator.cpp (Language : cpp)
  1. class escaped_list_separator_sjis
  2. {
  3.   private:
  4.     bool last_;
  5.     enum {
  6.         QUOTE     = '\"'// 囲み記号
  7.         SEPARATOR = ',',        // 区切り記号
  8.         ESCAPE    = '\\',       // エスケープ記号
  9.     };
  10.     // マルチバイトコードの1byte目判定
  11.     bool is_multibyte1(char c_)
  12.     {
  13.         unsigned char c = static_cast<unsigned char>(c_);
  14.         return ((c >= 0x81 && c <= 0x9f) || (c >= 0xe0 && c <= 0xfc));
  15.     }
  16.     // マルチバイトコードの2byte目判定
  17.     bool is_multibyte2(char c_)
  18.     {
  19.         unsigned char c = static_cast<unsigned char>(c_);
  20.         return ((c >= 0x40 && c <= 0x7e) || (c >= 0x80 && c <= 0xfc));
  21.     }
  22.     // エスケープ文字の処理
  23.     template <typename iterator, typename Token>
  24.     void do_escape(iterator& next, iterator end, Token& tok)
  25.     {
  26.         if (++next == end)
  27.             throw boost::escaped_list_error(std::string("cannot end with escape"));
  28.         switch (*next) {
  29.           case 'n':
  30.             tok += '\n';
  31.             break;
  32.           case QUOTE:
  33.           case SEPARATOR:
  34.           case ESCAPE:
  35.             tok += *next;
  36.             break;
  37.           default:
  38.             throw boost::escaped_list_error(std::string("unknown escape sequence"));
  39.         }
  40.     }
  41.     // マルチバイト文字の処理
  42.     template <typename iterator, typename Token>
  43.     void do_multibyte(iterator& next, iterator end, Token& tok)
  44.     {
  45.         tok += *next;
  46.         if (++next == end)
  47.             throw boost::escaped_list_error(std::string("cannot end with multi byte"));
  48.         if (is_multibyte2(*next)) {
  49.             tok += *next;
  50.         } else {
  51.             throw boost::escaped_list_error(std::string("unknown multi byte code"));
  52.         }
  53.     }
  54.   public:
  55.     escaped_list_separator_sjis(void) : last_(false)
  56.     {
  57.     }
  58.     void reset() {last_=false;}
  59.     template <typename InputIterator, typename Token>
  60.     bool operator()(InputIterator& next, InputIterator end, Token& tok)
  61.     {
  62.         bool bInQuote = false;
  63.         tok = Token();
  64.         if (next == end) {
  65.             if (last_) {
  66.                 last_ = false;
  67.                 return true;
  68.             } else {
  69.                 return false;
  70.             }
  71.         }
  72.         last_ = false;
  73.         for ( ; next != end; ++next) {
  74.             switch (*next) {
  75.               case ESCAPE:
  76.                 do_escape(next, end, tok);
  77.                 break;
  78.               case SEPARATOR:
  79.                 if (!bInQuote) {
  80.                     ++next;
  81.                     last_ = true;
  82.                     return true;
  83.                 } else {
  84.                     tok+=*next;
  85.                 }
  86.                 break;
  87.               case QUOTE:
  88.                 bInQuote=!bInQuote;
  89.                 break;
  90.               default:
  91.                 if (is_multibyte1(*next)) {
  92.                     do_multibyte(next, end, tok);
  93.                 } else {
  94.                     tok += *next;
  95.                 }
  96.                 break;
  97.             }
  98.                }
  99.         return true;
  100.     }
  101. };

Token.cpp (Language : cpp)
  1. vector<string> fromCSV2_3(const string& csv)
  2. {
  3.     vector<string> data;
  4.     boost::tokenizer<escaped_list_separator_sjis> tok(csv);
  5.     copy(tok.begin(), tok.end(), back_inserter(data));
  6.     return data;
  7. }

  위와 같은 클래스를 구현해주고, 해당 tokenizer에서 사용하는 TokenizerFunc Template 인자에 넣어주면 됩니다. 어차피 Template 이라는 자체가 결과적으로는 Define 치환결과하고 동일하다고 볼 수 있으니까요. 예전 strtok혹은 개별 토큰 기능을 구현하기 보다는 이런 클래스를 사용한다면 에러가 더 적게 발생하지 않을까요? 당연한 말이겠지만 이미 이런 클래스들은 여러사람이 사용하고 문제점은 이미 검증되었으니까요.
  만약 이런 클래스 구현을 통한 확장 아스키 지원이 조금 걸리면 Unicode 변환 -> Tokenizing -> 확장아스키 변환 이런 방법도 있겠네요. 사실 최근 대세가 Unicode로 돌아선지 꽤 되었다 보니, 이미 Unicode 형태로 처리를 하고 있을지도 모르겠습니다. 그래도 예전에 사용했거나 필요할 때 이 글을 보면서 구현을 하면 될 듯 하군요. 그럼 오늘도 즐거운 하루 되세요..^^

크리에이티브 커먼즈 라이센스
Creative Commons License
2008/06/18 11:58 2008/06/18 11:58

댓글을 달아 주세요