From c69e531ae7ffb7c4a6b72a4a3d91987efccd185b Mon Sep 17 00:00:00 2001 From: mete0r Date: Wed, 17 Apr 2019 21:42:16 +0900 Subject: [PATCH] hwp5.filestructure: node adaptations --- src/hwp5/filestructure.py | 423 ++++++++++++++++--------- src/hwp5/proc/models.py | 19 +- tests/hwp5_tests/test_binmodel.py | 7 +- tests/hwp5_tests/test_distdoc.py | 2 +- tests/hwp5_tests/test_filestructure.py | 49 +-- tests/hwp5_tests/test_recordstream.py | 7 +- tests/hwp5_tests/test_xmlmodel.py | 21 +- 7 files changed, 349 insertions(+), 179 deletions(-) diff --git a/src/hwp5/filestructure.py b/src/hwp5/filestructure.py index 1be11504..901901db 100644 --- a/src/hwp5/filestructure.py +++ b/src/hwp5/filestructure.py @@ -24,6 +24,7 @@ import sys from zope.interface import implementer +from zope.interface.registry import Components from .bintype import read_type from .compressed import decompress @@ -31,11 +32,10 @@ from .errors import InvalidOleStorageError from .errors import InvalidHwp5FileError from .interfaces import IHwp5File +from .interfaces import IStorageNode from .interfaces import IStorageDirectoryNode from .interfaces import IStorageStreamNode -from .storage import StorageWrapper from .storage import is_storage -from .storage import is_stream from .storage.ole import OleStorage from .summaryinfo import CLSID_HWP_SUMMARY_INFORMATION from .utils import GeneratorTextReader @@ -48,6 +48,7 @@ logger = logging.getLogger(__name__) +nodeAdapterRegistry = Components() HWP5_SIGNATURE = b'HWP Document File' + (b'\x00' * 15) @@ -125,7 +126,7 @@ def storage_is_hwp5file(stg): except KeyError: logger.info('stg has no FileHeader') return False - fileheader = HwpFileHeader(fileheader) + fileheader = HwpFileHeader(None, None, fileheader) if fileheader.signature == HWP5_SIGNATURE: return True else: @@ -133,31 +134,46 @@ def storage_is_hwp5file(stg): return False -class ItemConversionStorage(StorageWrapper): +@implementer(IStorageNode) +class Hwp5FileNode(object): - def __getitem__(self, name): - item = self.wrapped[name] - # 기반 스토리지에서 찾은 아이템에 대해, conversion()한다. - conversion = self.resolve_conversion_for(name) - if conversion: - node = conversion(item) - node.__name__ = name - node.__parent__ = self - return node - item.__name__ = name - item.__parent__ = self - return item - - def resolve_conversion_for(self, name): - ''' return a conversion function for the specified storage item ''' - pass + def __init__(self, hwp5file, parent, node): + self.hwp5file = hwp5file + self.__parent__ = parent + self.__name__ = node.__name__ + self.wrapped = node @implementer(IStorageStreamNode) -class CompressedStream(object): +class Hwp5FileStreamNode(Hwp5FileNode): + + def open(self): + return self.wrapped.open() + - def __init__(self, wrapped): - self.wrapped = wrapped +@implementer(IStorageDirectoryNode) +class Hwp5FileDirectoryNode(Hwp5FileNode): + + def __iter__(self): + return iter(self.wrapped) + + def __getitem__(self, name): + child = self.wrapped[name] + adapter = nodeAdapterRegistry.queryMultiAdapter( + [self.hwp5file, self, child], + IStorageNode, + name, + ) + if adapter is not None: + return adapter + return nodeAdapterRegistry.getMultiAdapter( + [self.hwp5file, self, child], + IStorageNode, + ) + + +@implementer(IStorageStreamNode) +class CompressedStream(Hwp5FileStreamNode): def open(self): return decompress(self.wrapped.open()) @@ -171,16 +187,9 @@ def other_formats(self): @implementer(IStorageDirectoryNode) -class CompressedStorage(StorageWrapper): +class CompressedStorage(Hwp5FileDirectoryNode): ''' decompress streams in the underlying storage ''' - def __getitem__(self, name): - item = self.wrapped[name] - if is_stream(item): - return CompressedStream(item) - else: - return item - def other_formats(self): try: other_formats = self.wrapped.other_formats @@ -190,10 +199,7 @@ def other_formats(self): @implementer(IStorageStreamNode) -class PasswordProtectedStream(object): - - def __init__(self, wrapped): - self.wrapped = wrapped +class PasswordProtectedStream(Hwp5FileStreamNode): def open(self): # TODO: 현재로선 암호화된 내용을 그냥 반환 @@ -209,17 +215,7 @@ def other_formats(self): return other_formats() -class PasswordProtectedStorage(StorageWrapper): - - def close(self): - self.wrapped.close() - - def __getitem__(self, name): - item = self.wrapped[name] - if is_stream(item): - return PasswordProtectedStream(item) - else: - return item +class PasswordProtectedStorage(Hwp5FileDirectoryNode): def other_formats(self): try: @@ -230,11 +226,11 @@ def other_formats(self): @implementer(IStorageStreamNode) -class VersionSensitiveItem(object): +class VersionSensitiveItem(Hwp5FileStreamNode): - def __init__(self, item, version): - self.wrapped = item - self.version = version + @property + def version(self): + return self.hwp5file.header.version def open(self): return self.wrapped.open() @@ -244,7 +240,7 @@ def other_formats(self): @implementer(IStorageStreamNode) -class Hwp5DistDocStream(VersionSensitiveItem): +class Hwp5DistDocStream(Hwp5FileStreamNode): def open(self): from hwp5.distdoc import decode @@ -304,12 +300,7 @@ def other_formats(self): @implementer(IStorageDirectoryNode) -class Hwp5DistDocStorage(ItemConversionStorage): - - def resolve_conversion_for(self, name): - def conversion(item): - return Hwp5DistDocStream(self.wrapped[name], None) # TODO: version - return conversion +class Hwp5DistDocStorage(Hwp5FileDirectoryNode): def other_formats(self): try: @@ -322,7 +313,10 @@ def other_formats(self): @implementer(IStorageStreamNode) class PreviewText(object): - def __init__(self, item): + def __init__(self, hwp5file, parent, item): + self.hwp5file = hwp5file + self.__parent__ = parent + self.__name__ = item.__name__ self.open = item.open def other_formats(self): @@ -361,19 +355,10 @@ def __unicode__(self): @implementer(IStorageDirectoryNode) -class Sections(ItemConversionStorage): +class Sections(Hwp5FileDirectoryNode): section_class = VersionSensitiveItem - def __init__(self, stg, version): - ItemConversionStorage.__init__(self, stg) - self.version = version - - def resolve_conversion_for(self, name): - def conversion(item): - return self.section_class(self.wrapped[name], self.version) - return conversion - def other_formats(self): return dict() @@ -404,7 +389,10 @@ def sections(self): @implementer(IStorageStreamNode) class HwpFileHeader(object): - def __init__(self, item): + def __init__(self, hwp5file, parent, item): + self.hwp5file = hwp5file + self.__parent__ = parent + self.__name__ = item.__name__ self.open = item.open def to_dict(self): @@ -449,7 +437,7 @@ def other_formats(self): return {'.txt': self.open_text} -class HwpSummaryInfo(VersionSensitiveItem): +class HwpSummaryInfo(Hwp5FileStreamNode): def other_formats(self): return {'.txt': self.open_text} @@ -562,7 +550,7 @@ def open_text(self): @implementer(IHwp5File) -class Hwp5File(ItemConversionStorage): +class Hwp5File(Hwp5FileDirectoryNode): ''' represents HWPv5 File Hwp5File(stg) @@ -577,7 +565,13 @@ def __init__(self, stg): errormsg = 'Not an HWP Document format v5 storage.' raise InvalidHwp5FileError(errormsg) - ItemConversionStorage.__init__(self, stg) + self.__parent__ = None + self.__name__ = '' + self.wrapped = stg + + @property + def hwp5file(self): + return self def close(self): self.wrapped.close() @@ -588,49 +582,6 @@ def header(self): fileheader = header - def resolve_conversion_for(self, name): - fn = None - - if name == 'FileHeader': - return HwpFileHeader - - if self.header.flags.password: - # TODO: 현재로선 decryption이 구현되지 않았으므로, - # 레코드 파싱은 불가능하다. 적어도 encrypted stream에 - # 직접 접근은 가능하도록, 다음 레이어들은 bypass한다. - if name in ('BinData', 'BodyText', 'Scripts', 'ViewText'): - return compose(PasswordProtectedStorage, fn) - elif name in ('DocInfo', ): - return compose(PasswordProtectedStream, fn) - - if self.header.flags.distributable: - if name in ('Scripts', 'ViewText'): - fn = compose(Hwp5DistDocStorage, fn) - - if self.header.flags.compressed: - if name in ('BinData', 'BodyText', 'ViewText'): - fn = compose(CompressedStorage, fn) - elif name == 'DocInfo': - fn = compose(CompressedStream, fn) - elif name == 'Scripts': - fn = compose(CompressedStorage, fn) - - if name == 'DocInfo': - fn = compose(self.with_version(self.docinfo_class), fn) - if name == 'BodyText': - fn = compose(self.with_version(self.bodytext_class), fn) - if name == 'ViewText': - fn = compose(self.with_version(self.bodytext_class), fn) - if name == 'PrvText': - return compose(PreviewText, fn) - if name == '\005HwpSummaryInformation': - return compose(self.with_version(self.summaryinfo_class), fn) - - return fn - - def with_version(self, f): - return with_version(f, self.header.version) - summaryinfo_class = HwpSummaryInfo docinfo_class = VersionSensitiveItem bodytext_class = Sections @@ -663,19 +614,21 @@ def text(self): return self.bodytext -class with_version(object): +class compose(object): - def __init__(self, fn, version): - self.fn = fn - self.version = version + def __init__(self, outerfn, innerfn): + self.outerfn = outerfn + self.innerfn = innerfn - def __call__(self, item): - return self.fn(item, self.version) + def __call__(self, hwp5file, parent, x): + outerfn = self.outerfn + innerfn = self.innerfn + return outerfn(hwp5file, parent, innerfn(hwp5file, parent, x)) def __repr__(self): - return '{}{}'.format( - self.fn.__name__, - self.version, + return 'compose({}, {})'.format( + self.outerfn.__name__, + self.innerfn.__name__, ) @property @@ -683,21 +636,109 @@ def __name__(self): return self.__repr__() -class compose(object): +# +# Generic +# - def __init__(self, outerfn, innerfn): - self.outerfn = outerfn - self.innerfn = innerfn +nodeAdapterRegistry.registerAdapter( + Hwp5FileNode, + [Hwp5File, Hwp5FileDirectoryNode, IStorageNode], + IStorageNode, +) - def __call__(self, x): - outerfn = self.outerfn - innerfn = self.innerfn - return outerfn(innerfn(x)) +nodeAdapterRegistry.registerAdapter( + Hwp5FileStreamNode, + [Hwp5File, Hwp5FileDirectoryNode, IStorageStreamNode], + IStorageNode, +) + +nodeAdapterRegistry.registerAdapter( + Hwp5FileDirectoryNode, + [Hwp5File, Hwp5FileDirectoryNode, IStorageDirectoryNode], + IStorageNode, +) + +# +# Specializations: Compressed, DistDoc, Password +# + +nodeAdapterRegistry.registerAdapter( + CompressedStream, + [Hwp5File, CompressedStorage, IStorageStreamNode], + IStorageNode, +) + +nodeAdapterRegistry.registerAdapter( + Hwp5DistDocStream, + [Hwp5File, Hwp5DistDocStorage, IStorageStreamNode], + IStorageNode, +) + +nodeAdapterRegistry.registerAdapter( + PasswordProtectedStream, + [Hwp5File, PasswordProtectedStorage, IStorageStreamNode], + IStorageNode, +) + +# +# Specialization: Text Sections +# + + +def adapt_text_section(hwp5file, parent, node): + return parent.section_class(hwp5file, parent, node) + + +nodeAdapterRegistry.registerAdapter( + adapt_text_section, + [Hwp5File, Sections, IStorageStreamNode], + IStorageNode, +) + +# +# Specializations: Hwp5File +# + +nodeAdapterRegistry.registerAdapter( + HwpFileHeader, + [Hwp5File, Hwp5File, IStorageStreamNode], + IStorageNode, + 'FileHeader', +) + + +class if_password_protected(object): + + def __init__(self, adapter, elseAdapter): + self.adapter = adapter + self.elseAdapter = elseAdapter def __repr__(self): - return 'compose({}, {})'.format( - self.outerfn.__name__, - self.innerfn.__name__, + return 'if_password_protected({}, {})'.format( + self.adapter.__name__, + self.elseAdapter.__name__, + ) + + @property + def __name__(self): + return self.__repr__() + + def __call__(self, hwp5file, parent, node): + if hwp5file.header.flags.password: + return self.adapter(hwp5file, parent, node) + else: + return self.elseAdapter(hwp5file, parent, node) + + +class if_(object): + + def __init__(self, adapter): + self.adapter = adapter + + def __repr__(self): + return '{}({})'.format( + type(self).__name__, + self.adapter.__name__, ) @property @@ -705,14 +746,108 @@ def __name__(self): return self.__repr__() -_compose = compose +class if_distdoc(if_): + + def __call__(self, hwp5file, parent, node): + if hwp5file.header.flags.distributable: + return self.adapter(hwp5file, parent, node) + else: + return node + + +class if_compressed(if_): + + def __call__(self, hwp5file, parent, node): + if hwp5file.header.flags.compressed: + return self.adapter(hwp5file, parent, node) + else: + return node -def compose(outerfn, innerfn): - if outerfn is None and innerfn is None: - raise ValueError() - if outerfn is None: - return innerfn - if innerfn is None: - return outerfn - return _compose(outerfn, innerfn) +def adapt_docinfo(hwp5file, parent, node): + return hwp5file.docinfo_class(hwp5file, parent, node) + + +def adapt_bodytext(hwp5file, parent, node): + return hwp5file.bodytext_class(hwp5file, parent, node) + + +def adapt_summaryinfo(hwp5file, parent, node): + return hwp5file.summaryinfo_class(hwp5file, parent, node) + + +nodeAdapterRegistry.registerAdapter( + if_password_protected(PasswordProtectedStorage, compose( + if_compressed(CompressedStorage), + if_distdoc(Hwp5DistDocStorage), + )), + [Hwp5File, Hwp5File, IStorageDirectoryNode], + IStorageNode, + 'Scripts', +) + + +# TODO: 현재로선 password decryption이 구현되지 않았으므로, +# 레코드 파싱은 불가능하다. 적어도 encrypted stream에 +# 직접 접근은 가능하도록, 다음 레이어들은 bypass한다. + +nodeAdapterRegistry.registerAdapter( + if_password_protected( + PasswordProtectedStorage, compose(adapt_bodytext, compose( + if_compressed(CompressedStorage), + if_distdoc(Hwp5DistDocStorage), + )) + ), + [Hwp5File, Hwp5File, IStorageDirectoryNode], + IStorageNode, + 'ViewText', +) + + +nodeAdapterRegistry.registerAdapter( + if_password_protected( + PasswordProtectedStorage, + if_compressed(CompressedStorage), + ), + [Hwp5File, Hwp5File, IStorageDirectoryNode], + IStorageNode, + 'BinData', +) + + +nodeAdapterRegistry.registerAdapter( + if_password_protected( + PasswordProtectedStorage, + compose(adapt_bodytext, if_compressed(CompressedStorage)), + ), + [Hwp5File, Hwp5File, IStorageDirectoryNode], + IStorageNode, + 'BodyText', +) + + +nodeAdapterRegistry.registerAdapter( + if_password_protected( + PasswordProtectedStream, + compose(adapt_docinfo, if_compressed(CompressedStream)), + ), + [Hwp5File, Hwp5File, IStorageStreamNode], + IStorageNode, + 'DocInfo', +) + + +nodeAdapterRegistry.registerAdapter( + PreviewText, + [Hwp5File, Hwp5File, IStorageStreamNode], + IStorageNode, + 'PrvText', +) + + +nodeAdapterRegistry.registerAdapter( + adapt_summaryinfo, + [Hwp5File, Hwp5File, IStorageStreamNode], + IStorageNode, + '\x05HwpSummaryInformation', +) diff --git a/src/hwp5/proc/models.py b/src/hwp5/proc/models.py index 71bdc502..b7cdb96c 100644 --- a/src/hwp5/proc/models.py +++ b/src/hwp5/proc/models.py @@ -24,6 +24,7 @@ from itertools import islice import sys +from zope.interface import implementer from zope.interface.registry import Components from ..binmodel import Hwp5File @@ -34,6 +35,7 @@ from ..cli import parse_recordstream_name from ..dataio import hexdump from ..filestructure import Hwp5FileOpener +from ..interfaces import IHwp5File from ..interfaces import IStorageOpener from ..storage import Open2Stream from ..treeop import ENDEVENT @@ -188,7 +190,22 @@ def stream_from_args(registry, args): else: stdin_binary = sys.stdin.buffer - yield ModelStream(Open2Stream(lambda: stdin_binary), version) + class DummyHwp5FileHeader: + def __init__(self, version): + self.version = version + + @implementer(IHwp5File) + class DummyHwp5File: + + header = fileheader = DummyHwp5FileHeader(version) + + stream = Open2Stream(lambda: stdin_binary) + stream.__name__ = '' + yield ModelStream( + DummyHwp5File(), + None, + stream, + ) def models_from_args(args): diff --git a/tests/hwp5_tests/test_binmodel.py b/tests/hwp5_tests/test_binmodel.py index 9a30ef3c..28496881 100644 --- a/tests/hwp5_tests/test_binmodel.py +++ b/tests/hwp5_tests/test_binmodel.py @@ -973,8 +973,11 @@ def test_generate_models_json_array(self): class TestModelStream(TestBase): @cached_property def docinfo(self): - return ModelStream(self.hwp5file_rec['DocInfo'], - self.hwp5file_rec.header.version) + return ModelStream( + self.hwp5file_rec, + self.hwp5file_rec, + self.hwp5file_rec['DocInfo'], + ) def test_models(self): self.assertEqual(67, len(list(self.docinfo.models()))) diff --git a/tests/hwp5_tests/test_distdoc.py b/tests/hwp5_tests/test_distdoc.py index 4d56b248..00b25e80 100644 --- a/tests/hwp5_tests/test_distdoc.py +++ b/tests/hwp5_tests/test_distdoc.py @@ -26,7 +26,7 @@ class TestHwp5DistDocFunctions(TestBase): @property def section(self): section = self.olestg['ViewText']['Section0'] - section = Hwp5DistDocStream(section, self.hwp5file.header.version) + section = Hwp5DistDocStream(None, None, section) return section def test_distdoc_decode_head_to_sha1(self): diff --git a/tests/hwp5_tests/test_filestructure.py b/tests/hwp5_tests/test_filestructure.py index 91c79c23..8851e43b 100644 --- a/tests/hwp5_tests/test_filestructure.py +++ b/tests/hwp5_tests/test_filestructure.py @@ -11,10 +11,13 @@ from hwp5 import filestructure as FS from hwp5.errors import InvalidHwp5FileError from hwp5.filestructure import CompressedStorage +from hwp5.filestructure import CompressedStream from hwp5.filestructure import Hwp5DistDocStream from hwp5.filestructure import Hwp5DistDocStorage from hwp5.filestructure import Hwp5File from hwp5.filestructure import Hwp5FileOpener +from hwp5.filestructure import Hwp5FileStreamNode +from hwp5.filestructure import Hwp5FileDirectoryNode from hwp5.filestructure import HwpFileHeader from hwp5.filestructure import PreviewText from hwp5.filestructure import Sections @@ -99,9 +102,11 @@ class TestHwp5DistDocStream(TestBase): @cached_property def jscriptversion(self): + hwp5file = self.hwp5file return Hwp5DistDocStream( + hwp5file, + hwp5file['Scripts'], self.olestg['Scripts']['JScriptVersion'], - self.hwp5file.header.version ) def test_head_record(self): @@ -139,7 +144,12 @@ class TestHwp5DistDicStorage(TestBase): @cached_property def scripts(self): - return Hwp5DistDocStorage(self.olestg['Scripts']) + hwp5file = self.hwp5file + return Hwp5DistDocStorage( + hwp5file, + hwp5file, + self.olestg['Scripts'] + ) def test_scripts_other_formats(self): jscriptversion = self.scripts['JScriptVersion'] @@ -151,9 +161,7 @@ class TestHwp5DistDoc(TestBase): hwp5file_name = 'viewtext.hwp' def test_conversion_for(self): - conversion = self.hwp5file.resolve_conversion_for('Scripts') - scripts = self.olestg['Scripts'] - scripts = conversion(scripts) + scripts = self.hwp5file['Scripts'] self.assertTrue( isinstance(scripts.wrapped, Hwp5DistDocStorage) ) @@ -193,7 +201,8 @@ def test_getitem(self): class TestCompressedStorage(TestBase): def test_getitem(self): - stg = FS.CompressedStorage(self.olestg['BinData']) + hwp5file = self.hwp5file + stg = CompressedStorage(hwp5file, hwp5file, self.olestg['BinData']) self.assertTrue(is_directory(stg)) item = stg['BIN0002.jpg'] @@ -268,9 +277,12 @@ def test_fileheader(self): def test_getitem_storage_classes(self): hwp5file = self.hwp5file - self.assertTrue(isinstance(hwp5file['BinData'], FS.StorageWrapper)) - self.assertTrue(isinstance(hwp5file['BodyText'], FS.Sections)) - self.assertTrue(isinstance(hwp5file['Scripts'], FS.StorageWrapper)) + self.assertTrue(isinstance(hwp5file['FileHeader'], HwpFileHeader)) + self.assertTrue(isinstance(hwp5file['BinData'], Hwp5FileDirectoryNode)) + self.assertTrue(isinstance(hwp5file['BodyText'], Sections)) + self.assertTrue(isinstance(hwp5file['Scripts'], Hwp5FileDirectoryNode)) + self.assertTrue(isinstance(hwp5file['PrvText'], PreviewText)) + self.assertTrue(isinstance(hwp5file['PrvImage'], Hwp5FileStreamNode)) def test_prv_text(self): prvtext = self.hwp5file['PrvText'] @@ -308,9 +320,6 @@ def test_if_hwp5file_contains_other_formats(self): stg = ExtraItemStorage(self.hwp5file) self.assertTrue('PrvText.utf8' in list(stg)) - def test_resolve_conversion_for_bodytext(self): - self.assertTrue(self.hwp5file.resolve_conversion_for('BodyText')) - def test_docinfo(self): hwp5file = self.hwp5file self.assertTrue(isinstance(hwp5file.docinfo, FS.VersionSensitiveItem)) @@ -325,18 +334,12 @@ def test_docinfo(self): def test_bodytext(self): bodytext = self.hwp5file.bodytext - self.assertTrue(isinstance(bodytext, FS.Sections)) + self.assertTrue(isinstance(bodytext, Sections)) + self.assertTrue(isinstance(bodytext.wrapped, CompressedStorage)) self.assertEqual(['Section0'], list(bodytext)) - - -class TestSections(TestBase): - - @property - def sections(self): - return Sections( - self.hwp5file.stg['BodyText'], - self.hwp5file.header.version, - ) + section0 = bodytext['Section0'] + self.assertTrue(isinstance(section0, Hwp5FileStreamNode)) + self.assertTrue(isinstance(section0.wrapped, CompressedStream)) class TestGeneratorReader(TestCase): diff --git a/tests/hwp5_tests/test_recordstream.py b/tests/hwp5_tests/test_recordstream.py index 3da7885d..64ba3153 100644 --- a/tests/hwp5_tests/test_recordstream.py +++ b/tests/hwp5_tests/test_recordstream.py @@ -50,8 +50,11 @@ class TestRecordStream(TestBase): @cached_property def docinfo(self): - return RecordStream(self.hwp5file_fs['DocInfo'], - self.hwp5file_fs.header.version) + return RecordStream( + self.hwp5file_fs, + self.hwp5file_fs, + self.hwp5file_fs['DocInfo'], + ) def test_records(self): self.assertEqual(67, len(list(self.docinfo.records()))) diff --git a/tests/hwp5_tests/test_xmlmodel.py b/tests/hwp5_tests/test_xmlmodel.py index a2679854..4a13850a 100644 --- a/tests/hwp5_tests/test_xmlmodel.py +++ b/tests/hwp5_tests/test_xmlmodel.py @@ -78,8 +78,11 @@ class TestModelEventStream(TestBase): @cached_property def docinfo(self): - return ModelEventStream(self.hwp5file_bin['DocInfo'], - self.hwp5file_bin.header.version) + return ModelEventStream( + self.hwp5file_bin, + self.hwp5file_bin, + self.hwp5file_bin['DocInfo'], + ) def test_modelevents(self): self.assertEqual(len(list(self.docinfo.models())) * 2, @@ -91,8 +94,11 @@ class TestDocInfo(TestBase): @cached_property def docinfo(self): - return DocInfo(self.hwp5file_bin['DocInfo'], - self.hwp5file_bin.header.version) + return DocInfo( + self.hwp5file_bin, + self.hwp5file_bin, + self.hwp5file_bin['DocInfo'], + ) def test_events(self): events = list(self.docinfo.events()) @@ -114,8 +120,11 @@ def test_events_with_embedbin(self): class TestSection(TestBase): def test_events(self): - section = Section(self.hwp5file_bin['BodyText']['Section0'], - self.hwp5file_bin.fileheader.version) + section = Section( + self.hwp5file_bin, + self.hwp5file_bin['BodyText'], + self.hwp5file_bin['BodyText']['Section0'], + ) events = list(section.events()) ev, (tag, attrs, ctx) = events[0] self.assertEqual((STARTEVENT, SectionDef), (ev, tag))