Hwp 파일의 레코드 구조 - hallazzang/hwp5-table-extractor GitHub Wiki
Hwp 파일의 몇몇 스트림(DocInfo
나 BodyText/SectionN
, DocHistory/VersionLogN
)은 연속된 레코드 구조를 가진다.
레코드 하나의 구조는 다음과 같다:
┌────────┬─────────┐
Record: │ Header │ Payload │
└────┬───┴─────────┘
┌────┴───┬───────┬──────┐
Header: │ Tag ID │ Level │ Size │
└────────┴───────┴──────┘
즉 레코드 하나는 헤더와 데이터(앞으로 Payload라고 부르겠다)로 이루어져 있고, 여기서 헤더는 다시 태그 아이디, 레벨, 그리고 크기로 이루어져 있다.
또한, 레코드의 헤더는 다음과 같이 볼 수 있다:
비트 범위 | 설명 |
---|---|
0~9 | 태그 아이디 |
10~19 | 레벨 |
20~31 | 크기 |
여기서 만약 크기가 0xfff
(4095)라면 Payload의 크기가 4095 바이트 이상인 경우로, 이후에 나타나는 4바이트를 크기로 인식한다.
즉 Payload의 크기가 4095 바이트 이상인 경우에 레코드는 다음과 같은 구조를 가진다.
┌────────┬──────┬─────────┐
Record: │ Header │ Size │ Payload │
└────┬───┴──────┴─────────┘
┌────┴───┬───────┬───────┐
Header: │ Tag ID │ Level │ 0xfff │
└────────┴───────┴───────┘
예제 코드
실제로 Hwp 파일이 위와 같은 구조를 가지는지 확인해보자.
import struct
import zlib
from io import BytesIO
import olefile
ole = olefile.OleFileIO('test.hwp')
stream = ole.openstream('DocInfo')
stream = BytesIO(zlib.decompress(stream.read(), -15))
while True:
header = stream.read(4)
if not header: # 파일이 끝남(EOF)
break
header = struct.unpack('<I', header)[0]
tag_id = header & 0x3ff
level = (header >> 10) & 0x3ff
size = (header >> 20) & 0xfff
if size == 0xfff:
size = struct.unpack('<I', stream.read(4))[0]
payload = stream.read(size)
print('Tag ID: %d, Level: %d, Size: %d(bytes)' % (
tag_id, level, size
))
결과는 아마 다음과 같을 것이다:
Tag ID: 16, Level: 0, Size: 26(bytes)
Tag ID: 17, Level: 0, Size: 64(bytes)
Tag ID: 19, Level: 1, Size: 29(bytes)
Tag ID: 19, Level: 1, Size: 37(bytes)
Tag ID: 19, Level: 1, Size: 29(bytes)
Tag ID: 19, Level: 1, Size: 51(bytes)
Tag ID: 19, Level: 1, Size: 31(bytes)
...
태그 아이디와 레벨에 관해서는 다른 페이지에서 설명한다. (작성 중)