1편이 올라간지 한참이나 지나서어 2편을 올리게 되서 죄송합니다. 대략 8개월이나 지났군요. 이제와서 연재(?)를 계속한다고 해도 의미가 있을지는 모르겠지만, 그래도 미완성으로 남겨둘 수는 없기에 늦었지만 계속 이어나가겠습니다.
관련글: [Simple XML 파서 만들기] #1. 파싱 원리
이번 건 XML 파싱과는 크게 상관은 없지만, 본격으로 XML 파서를 만들기 전에 먼저 필요한 것들을 점검해 보겠습니다.
1. Stream 라이브러리
XML 문서에 대한 파싱을 수월하게 하기 위해선, 전체 데이터로 부터 순서대로 한 문자씩 읽어와주는 읽기전용 Stream이 필요합니다. 그리고 입력 소스를 다양화 할 수 있고, 여러 종류의 Stream을 조합해서 여러가지 디코딩 방식을 지원할 수 있게 됩니다.
저는 Stream 클래스를 직접 만들어서 사용했습니다만, 관련 라이브러리가 이미 준비되어 있다면 이 부분은 건너 뛰어도 됩니다. 단, 앞으로 보여줄 소스들은 자체 제작한 Stream 라이브러리를 쓰고 있기 때문에, 이 부분은 적절히 수정해서 적용해야 됩니다.
다음은 제가 간단히 만든 Stream 클래스의 일부입니다.
template<typename T>
class IReadStream
{
public:
virtual ~IReadStream() {}
virtual T Next() = 0;
virtual T Current() const = 0;
virtual bool isEnd() const = 0;
};
template<typename T>
class CFileReader : IReadStream<T>
{
protected:
HANDLE hSourceFile;
T data;
public:
CFileReader(HANDLE aFileHandle) { hSourceFile = aFileHandle; }
virtual ~CFileReader() {}
T Next() {
DWORD readbytes = 0;
BOOL result;
const int data_size = sizeof(T);
result = ::ReadFile(hSourceFile, &data, data_size,
&readbytes, NULL);
return (result && data_size == readbytes) ? data : (data = NULL);
}
T Current() const { return data; }
bool isEnd() const { return Current() == NULL; }
HANDLE GetHandle() const { return hSourceFile; }
};
/*--- 사용 예 ----*
HANDLE hFile = CreateFileW(
L"filename", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, NULL
);
CFileReader<wchar_t> file_stream(hFile);
while (file_stream.Next());
{
wchar_t data = file_stream.Current();
...
}
*--------------*/
2. UTF-8 디코딩
이 파서는 유니코드를 기반으로 하고 있고, UTF-8 형식으로 만들어진 XML 문서도 상당수 존재하기 때문에 UTF-8 디코딩을 위한 스트림을 추가로 작성해 두었습니다. (UTF-8 디코딩에 관해서는 여기를 참조)
typedef IReadStream<BYTE> IByteReadStream;
typedef IReadStream<wchar_t> IWStringReadStream;
class CUTF8DecodeStream: public IWStringReadStream
{
protected:
IByteReadStream* source;
wchar_t data;
public:
CUTF8DecodeStream(IByteReadStream& source_stream)
{
source = &source_stream;
data = NULL;
}
wchar_t Next();
wchar_t Next(int aCount)
{
if (aCount < 1 || isEnd()) return NULL;
for (int i = 1; i <= aCount; i++) Next();
return Current();
}
wchar_t Current() const { return data; }
bool isEnd() const { return Current() == NULL; }
};
//--- CUTF8DecodeStream class ---//
wchar_t CUTF8DecodeStream::Next()
{
BYTE firstByte, secondByte, thirdByte;
if (firstByte = source->Next())
{
const unsigned int count = GetUTF8ByteCount(firstByte);
switch (count)
{
case 1: break;
case 2:
secondByte = source->Next();
break;
case 3:
secondByte = source->Next();
thirdByte = source->Next();
break;
default:
{ for (int i = 0; i < count; ++i) source->Next(); }
return 0xFFFF;
}
}
return DecodeUTF8(firstByte, secondByte, thirdByte);
}
BYTE GetUTF8ByteCount(BYTE first)
{
// U-00000000 - U-0000007F: 0xxxxxxx
// U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
// U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
// U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
// U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
// U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if (first < 0x80U) return 1;
else if (first < 0xE0U) return 2;
else if (first < 0xF0U) return 3;
else if (first < 0xF8U) return 4;
else if (first < 0xFCU) return 5;
else return 6;
}
wchar_t DecodeUTF8(BYTE first, BYTE second, BYTE third)
{
switch( GetUTF8ByteCount(first) )
{
case 1: return first;
case 2: if (second)
return ((first & 0x1F) << 6) | (second & 0x3F);
case 3: if (second && third)
return ((first & 0x0F) << 12) |
((second & 0x3F) << 6) | third & 0x3F;
}
return NULL;
}
/*--- 사용 예 ----*
HANDLE hFile = CreateFileW(
L"filename", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, NULL
);
CFileReader<BYTE> file_stream(hFile);
CUTF8DecodeStream utf8_stram(file_stream);
while (utf8_stram.Next());
{
wchar_t data = utf8_stram.Current();
...
}
*--------------*/
3. String Buffer
마지막으로 문자열 버퍼 클래스를 준비해야 합니다. 이 클래스의 역할은 단순히 한 문자씩 입력받아서 버퍼에 쌓아두고, 나중에 한번에 문자열로 받아오게 해주는 것 뿐입니다. 사실 std::wstring을 아주 가볍게 캡슐화 시킨 것에 불과하고, std::wstring을 그대로 사용해도 별로 상관은 없습니다.
class CWStringBuffer
{
private:
std::wstring buffer;
unsigned int size;
public:
CWStringBuffer() : size(0) {}
CWStringBuffer(unsigned int aSize);
virtual ~CWStringBuffer();
void Capacity(unsigned int aSize);
inline size_t Capacity() { return size; }
inline size_t Length() { return buffer.length(); }
inline bool isEmpty() { return buffer.empty(); }
inline bool isFull() { return size && ( Length() >= (Capacity()-1) ); }
void Clear() { if (!isEmpty()) buffer.clear(); }
bool Push(wchar_t aWChar);
inline const std::wstring& GetString() { return buffer; }
wchar_t GetLastChar();
};
/*--- CWStringBuffer ---*/
CWStringBuffer::CWStringBuffer(unsigned int aSize) { Capacity(aSize); }
CWStringBuffer::~CWStringBuffer() { Clear(); }
void CWStringBuffer::Capacity(unsigned int aSize)
{
size = aSize;
Clear();
buffer.reserve(aSize);
}
bool CWStringBuffer::Push(wchar_t aWChar)
{
if (isFull()) return false;
buffer.push_back(aWChar);
return true;
}
wchar_t CWStringBuffer::GetLastChar()
{
return (!buffer.empty()) ? buffer[Length()-1] : NULL;
}




최근 덧글