안녕하세요? 김백일입니다.
지난번(342번)글에서 AnsiString용 토크나이저를 만들 때는,
TStrings 객체에 모든 토큰을 전부 저장하는 구조로 만들었습니다.
이런 구조는 대부분의 경우에 편리하기는 하지만,
경우에 따라서는 한번의 함수 호출에, 하나의 토큰 만을 얻어오는 인터페이스도
필요할 때가 있습니다.
strtok()나 자바의 StringTokenizer 같은 경우가 그런 예이죠.
(XML을 해보신분을 아시겠지만, 이건 마치 DOM과 SAX의 차이와 비슷합니다.)
이번에는 특히, 자바에서 C++로 전향하신 분들을 위해
자바의 StringTokenizer와 비슷한 클래스를 만들어 보았습니다.
StringTokenizer는 자바를 조금이라도 해보신 분들은 다 아실만한 클래스입니다.
자세한 내용은 다음 JDK의 다큐먼트를 참고하세요.
http://java.sun.com/j2se/1.4/docs/api/java/util/StringTokenizer.html
저는 이 StringTokenizer의 구조에 대해 한가지 불만이 있는데요,
토큰과 구분자(delimiter)를 얻는 인터페이스를 다르게하지 않고,
StringTokenizer 생성자의 인자인 returnDelims를 true로 하여
토큰과 구분자의 구별이 없이 리턴받는 점입니다.
리턴받은 다음에 if문으로 다시 검사를 해야하는 귀찮은 점이 있지요.
(물론 지난번 글의 토크나이저는 구분자를 아예 리턴하지도 않았지만요.
이유는? 단지 귀찮아서... -_-;;)
그래서 이번에 올리는 AnsiString용 StringTokenizer의 인터페이스는
이부분의 인터페이스를 자바의 것과 다르게 구현했습니다.
자바 소스에서 변환하실 때 이부분만 유의해서 보시면 되겠습니다.
//---------------------------------------------------------------------------
// 지난번(341번) 글에 있는
// FirstDelimiter(),FirstNotDelimiter() 정의가 필요합니다.
//---------------------------------------------------------------------------
const AnsiString DefaultDelim("\t\v\f\r\n ");
class StringTokenizer
{
public:
StringTokenizer(AnsiString str)
: Str(str), N(str.Length()), Delim(DefaultDelim), Start(1), Stop(0) {}
StringTokenizer(AnsiString str, AnsiString delim)
: Str(str), N(str.Length()), Delim(delim), Start(1), Stop(0) {}
int countTokens() const;
bool hasMoreTokens();
char nextToken(AnsiString& token, AnsiString delim);
char nextToken(AnsiString& token) { return nextToken(token, Delim); }
protected:
AnsiString Str, Delim;
int Start, Stop, N;
};
inline bool StringTokenizer::hasMoreTokens()
{
Start = Stop < 1 ? FirstNotDelimiter(Delim, Str) :
FirstNotDelimiter(Delim, Stop + 1, Str);
Stop = FirstDelimiter(Delim, Start, Str);
return 1 <= Start && Start <= N;
}
// 이 부분이 자바와 다릅니다.
// 토큰은 이 함수의 token 인자로 출력하고,
// 구분자는 리턴값으로 출력합니다.
// 이렇게 하는 것이 훨씬 이해하고 쉽고 편리하지요.
char StringTokenizer::nextToken(AnsiString& token, AnsiString Delim)
{
char cRet;
if (Stop < 1 || Stop > N) {
Stop = N + 1;
cRet = '\0';
} else {
cRet = Str[Stop];
}
token = Str.SubString(Start, Stop - Start);
return cRet;
}
int StringTokenizer::countTokens() const
{
int start, stop, count = 0;
for(start = FirstNotDelimiter(Delim, Str);
1 <= start && start <= N;
start = FirstNotDelimiter(Delim, stop + 1, Str)) {
stop = FirstDelimiter(Delim, start, Str);
if (stop < 1 || stop > N + 1) stop = N;
count++;
}
return count;
}
//---------------------------------------------------------------------------
#pragma argsused
int main(int argc, char* argv[])
{
AnsiString token, sentence = "This sentence contains five words.";
char retDelim;
StringTokenizer st(sentence);
cout << st.countTokens() << endl;
while (st.hasMoreTokens()) {
retDelim = st.nextToken(token);
cout << token << " \"" << retDelim << "\"" << endl;
}
return 0;
}
//---------------------------------------------------------------------------