| |
|
|
| from io import BytesIO |
| from unittest import TestCase |
| import binascii |
| import codecs |
| import json |
| import sys |
|
|
| from hwp5.binmodel import BinData |
| from hwp5.binmodel import BorderFill |
| from hwp5.binmodel import Control |
| from hwp5.binmodel import ControlChar |
| from hwp5.binmodel import ControlData |
| from hwp5.binmodel import FaceName |
| from hwp5.binmodel import GShapeObjectControl |
| from hwp5.binmodel import HeaderParagraphList |
| from hwp5.binmodel import Hwp5File |
| from hwp5.binmodel import LanguageStruct |
| from hwp5.binmodel import ListHeader |
| from hwp5.binmodel import ModelStream |
| from hwp5.binmodel import ParaLineSegList |
| from hwp5.binmodel import ParaText |
| from hwp5.binmodel import Paragraph |
| from hwp5.binmodel import RecordModel |
| from hwp5.binmodel import ShapeComponent |
| from hwp5.binmodel import Style |
| from hwp5.binmodel import TableBody |
| from hwp5.binmodel import TableCaption |
| from hwp5.binmodel import TableCell |
| from hwp5.binmodel import TableControl |
| from hwp5.binmodel import TextboxParagraphList |
| from hwp5.binmodel import init_record_parsing_context |
| from hwp5.binmodel import model_to_json |
| from hwp5.binmodel import parse_model |
| from hwp5.binmodel import parse_models |
| from hwp5.binmodel import parse_models_intern |
| from hwp5.dataio import Enum |
| from hwp5.dataio import Flags |
| from hwp5.dataio import UINT32 |
| from hwp5.dataio import WORD |
| from hwp5.recordstream import Record |
| from hwp5.recordstream import read_records |
| from hwp5.tagids import HWPTAG_BEGIN |
| from hwp5.treeop import STARTEVENT, ENDEVENT |
| from hwp5.treeop import prefix_event |
| from hwp5.utils import cached_property |
|
|
| from . import test_recordstream |
| from .fixtures import get_fixture_path |
|
|
|
|
|
|
|
|
|
|
| def TestContext(**ctx): |
| ''' test context ''' |
| if 'version' not in ctx: |
| ctx['version'] = (5, 0, 0, 0) |
| return ctx |
|
|
|
|
| testcontext = TestContext() |
|
|
|
|
| class TestRecordParsing(TestCase): |
| def test_init_record_parsing_context(self): |
| record = dict(tagid=HWPTAG_BEGIN, payload=b'abcd') |
| context = init_record_parsing_context(testcontext, record) |
|
|
| self.assertEqual(record, context['record']) |
| self.assertEqual(b'abcd', context['stream'].read()) |
|
|
|
|
| class BinEmbeddedTest(TestCase): |
| ctx = TestContext() |
| stream = BytesIO(b'\x12\x04\xc0\x00\x01\x00\x02\x00\x03\x00' |
| b'\x6a\x00\x70\x00\x67\x00') |
|
|
| def testParse(self): |
| record = next(read_records(self.stream)) |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| self.assertTrue(BinData, model['type']) |
| self.assertEqual(BinData.StorageType.EMBEDDING, |
| BinData.Flags(model['content']['flags']).storage) |
| self.assertEqual(2, model['content']['bindata']['storage_id']) |
| self.assertEqual('jpg', model['content']['bindata']['ext']) |
|
|
|
|
| class LanguageStructTest(TestCase): |
| def test_cls_dict_has_attributes(self): |
| FontFace = LanguageStruct(b'FontFace', WORD) |
| self.assertTrue('attributes' in FontFace.__dict__) |
|
|
|
|
| class TestBase(test_recordstream.TestBase): |
|
|
| @cached_property |
| def hwp5file_bin(self): |
| return Hwp5File(self.olestg) |
|
|
| hwp5file = hwp5file_bin |
|
|
|
|
| class FaceNameTest(TestBase): |
| hwp5file_name = 'facename.hwp' |
|
|
| def test_font_file_type(self): |
|
|
| docinfo = self.hwp5file.docinfo |
| facenames = (model for model in docinfo.models() |
| if model['type'] is FaceName) |
| facenames = list(facenames) |
|
|
| facename = facenames[0]['content'] |
| self.assertEqual(u'굴림', facename['name']) |
| self.assertEqual(FaceName.FontFileType.TTF, |
| facename['flags'].font_file_type) |
|
|
| facename = facenames[3]['content'] |
| self.assertEqual(u'휴먼명조', facename['name']) |
| self.assertEqual(FaceName.FontFileType.HFT, |
| facename['flags'].font_file_type) |
|
|
| facename = facenames[4]['content'] |
| self.assertEqual(u'한양신명조', facename['name']) |
| self.assertEqual(FaceName.FontFileType.HFT, |
| facename['flags'].font_file_type) |
|
|
|
|
| class DocInfoTest(TestBase): |
| hwp5file_name = 'facename2.hwp' |
|
|
| def test_charshape_lang_facename(self): |
|
|
| docinfo = self.hwp5file.docinfo |
| styles = list(m for m in docinfo.models() |
| if m['type'] is Style) |
|
|
| def style_lang_facename(style, lang): |
| charshape_id = style['content']['charshape_id'] |
| return docinfo.charshape_lang_facename(charshape_id, lang) |
|
|
| def style_lang_facename_name(style, lang): |
| facename = style_lang_facename(style, lang) |
| return facename['content']['name'] |
|
|
| self.assertEqual(u'바탕', style_lang_facename_name(styles[0], 'ko')) |
| self.assertEqual(u'한컴돋움', style_lang_facename_name(styles[1], 'ko')) |
| self.assertEqual(u'Times New Roman', |
| style_lang_facename_name(styles[2], 'en')) |
| self.assertEqual(u'Arial', style_lang_facename_name(styles[3], 'en')) |
| self.assertEqual(u'해서 약자', style_lang_facename_name(styles[4], 'cn')) |
| self.assertEqual(u'해서 간자', style_lang_facename_name(styles[5], 'cn')) |
| self.assertEqual(u'명조', style_lang_facename_name(styles[6], 'jp')) |
| self.assertEqual(u'고딕', style_lang_facename_name(styles[7], 'jp')) |
|
|
|
|
| class BorderFillTest(TestBase): |
| hwp5file_name = 'borderfill.hwp' |
|
|
| def test_parse_borderfill(self): |
|
|
| docinfo = self.hwp5file.docinfo |
| borderfills = (model for model in docinfo.models() |
| if model['type'] is BorderFill) |
| borderfills = list(borderfills) |
|
|
| section = self.hwp5file.bodytext.section(0) |
| tablecells = list(model for model in section.models() |
| if model['type'] is TableCell) |
| for tablecell in tablecells: |
| borderfill_id = tablecell['content']['borderfill_id'] |
| borderfill = borderfills[borderfill_id - 1]['content'] |
| tablecell['borderfill'] = borderfill |
|
|
| borderfill = tablecells[0]['borderfill'] |
| self.assertEqual(0, borderfill['fillflags']) |
| self.assertEqual(None, borderfill.get('fill_colorpattern')) |
| self.assertEqual(None, borderfill.get('fill_gradation')) |
| self.assertEqual(None, borderfill.get('fill_image')) |
|
|
| borderfill = tablecells[1]['borderfill'] |
| self.assertEqual(1, borderfill['fillflags']) |
| self.assertEqual(dict(background_color=0xff7f3f, |
| pattern_color=0, |
| pattern_type_flags=0xffffffff), |
| borderfill['fill_colorpattern']) |
| self.assertEqual(None, borderfill.get('fill_gradation')) |
| self.assertEqual(None, borderfill.get('fill_image')) |
|
|
| borderfill = tablecells[2]['borderfill'] |
| self.assertEqual(4, borderfill['fillflags']) |
| self.assertEqual(None, borderfill.get('fill_colorpattern')) |
| self.assertEqual(dict(blur=40, center=dict(x=0, y=0), |
| colors=[0xff7f3f, 0], |
| shear=90, type=1), |
| borderfill['fill_gradation']) |
| self.assertEqual(None, borderfill.get('fill_image')) |
|
|
| borderfill = tablecells[3]['borderfill'] |
| self.assertEqual(2, borderfill['fillflags']) |
| self.assertEqual(None, borderfill.get('fill_colorpattern')) |
| self.assertEqual(None, borderfill.get('fill_gradation')) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| borderfill.get('fill_image')) |
|
|
| borderfill = tablecells[4]['borderfill'] |
| self.assertEqual(3, borderfill['fillflags']) |
| self.assertEqual(dict(background_color=0xff7f3f, |
| pattern_color=0, |
| pattern_type_flags=0xffffffff), |
| borderfill['fill_colorpattern']) |
| self.assertEqual(None, borderfill.get('fill_gradation')) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| borderfill.get('fill_image')) |
|
|
| borderfill = tablecells[5]['borderfill'] |
| self.assertEqual(6, borderfill['fillflags']) |
| self.assertEqual(None, borderfill.get('fill_colorpattern')) |
| self.assertEqual(dict(blur=40, center=dict(x=0, y=0), |
| colors=[0xff7f3f, 0], |
| shear=90, type=1), |
| borderfill['fill_gradation']) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| borderfill.get('fill_image')) |
|
|
|
|
| class StyleTest(TestBase): |
| hwp5file_name = 'charstyle.hwp' |
|
|
| def test_charstyle(self): |
|
|
| docinfo = self.hwp5file.docinfo |
| styles = (model for model in docinfo.models() |
| if model['type'] is Style) |
| styles = list(styles) |
|
|
| style = styles[0]['content'] |
| self.assertEqual(dict(name='Normal', |
| unknown=0, |
| parashape_id=0, |
| charshape_id=1, |
| next_style_id=0, |
| lang_id=1042, |
| flags=0, |
| local_name=u'바탕글'), |
| style) |
| charstyle = styles[13]['content'] |
| self.assertEqual(dict(name='', |
| unknown=0, |
| parashape_id=0, |
| charshape_id=1, |
| next_style_id=0, |
| lang_id=1042, |
| flags=1, |
| local_name=u'글자스타일'), |
| charstyle) |
|
|
|
|
| class ParaCharShapeTest(TestBase): |
|
|
| @property |
| def paracharshape_record(self): |
| return self.bodytext.section(0).record(2) |
|
|
| def test_read_paracharshape(self): |
| parent_context = dict() |
| parent_model = dict(content=dict(charshapes=5)) |
|
|
| record = self.paracharshape_record |
| context = init_record_parsing_context(dict(), record) |
| context['parent'] = parent_context, parent_model |
| model = record |
| parse_model(context, model) |
| self.assertEqual(dict(charshapes=[(0, 7), (19, 8), (23, 7), (24, 9), |
| (26, 7)]), |
| model['content']) |
|
|
|
|
| class TableTest(TestBase): |
|
|
| @property |
| def stream(self): |
| return BytesIO(b'G\x04\xc0\x02 lbt\x11#*\x08\x00\x00\x00\x00\x00\x00' |
| b'\x00\x00\x06\x9e\x00\x00D\x10\x00\x00\x00\x00\x00\x00' |
| b'\x1b\x01\x1b\x01\x1b\x01\x1b\x01\xed\xad\xa2V\x00\x00' |
| b'\x00\x00') |
|
|
| @cached_property |
| def tablecontrol_record(self): |
| return self.bodytext.section(0).record(30) |
|
|
| @cached_property |
| def tablecaption_record(self): |
| return self.bodytext.section(0).record(68) |
|
|
| @cached_property |
| def tablebody_record(self): |
| return self.bodytext.section(0).record(31) |
|
|
| @cached_property |
| def tablecell_record(self): |
| return self.bodytext.section(0).record(32) |
|
|
| def testParsePass1(self): |
| record = next(read_records(self.stream)) |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| self.assertTrue(TableControl, model['type']) |
| self.assertEqual(1453501933, model['content']['instance_id']) |
| self.assertEqual(0x0, model['content']['x']) |
| self.assertEqual(0x0, model['content']['y']) |
| self.assertEqual(0x1044, model['content']['height']) |
| self.assertEqual(0x9e06, model['content']['width']) |
| self.assertEqual(0, model['content']['unknown1']) |
| self.assertEqual(0x82a2311, model['content']['flags']) |
| self.assertEqual(0, model['content']['z_order']) |
| self.assertEqual(dict(left=283, right=283, top=283, bottom=283), |
| model['content']['margin']) |
| self.assertEqual('tbl ', model['content']['chid']) |
|
|
| def test_parse_child_table_body(self): |
| record = self.tablecontrol_record |
| context = init_record_parsing_context(testcontext, record) |
|
|
| tablebody_record = self.tablebody_record |
| child_context = init_record_parsing_context(testcontext, |
| tablebody_record) |
| child_model = dict(type=TableBody, content=dict()) |
| child = (child_context, child_model) |
|
|
| self.assertFalse(context.get('seen_table_body')) |
| TableControl.on_child(dict(), context, child) |
| |
| |
| self.assertTrue(context['seen_table_body']) |
| |
| self.assertEqual(dict(), child_model['content']) |
|
|
| def test_parse_child_table_cell(self): |
| record = self.tablecontrol_record |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| context['seen_table_body'] = True |
|
|
| child_record = self.tablecell_record |
| child_context = init_record_parsing_context(testcontext, child_record) |
| child_model = child_record |
| child_context['parent'] = context, model |
| parse_model(child_context, child_model) |
| self.assertEqual(TableCell, child_model['type']) |
| self.assertEqual(TableCell, child_model['type']) |
| self.assertEqual(dict(padding=dict(top=141, right=141, bottom=141, |
| left=141), |
| rowspan=1, |
| colspan=1, |
| borderfill_id=1, |
| height=282, |
| listflags=32, |
| width=20227, |
| unknown1=0, |
| unknown_width=20227, |
| paragraphs=1, |
| col=0, |
| row=0), child_model['content']) |
| self.assertEqual(b'', child_context['stream'].read()) |
|
|
| def test_parse_child_table_caption(self): |
| record = self.tablecontrol_record |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| context['seen_table_body'] = False |
|
|
| child_record = self.tablecaption_record |
| child_context = init_record_parsing_context(testcontext, child_record) |
| child_context['parent'] = context, model |
| child_model = child_record |
| parse_model(child_context, child_model) |
| self.assertEqual(TableCaption, child_model['type']) |
| self.assertEqual(dict(listflags=0, |
| width=8504, |
| max_width=40454, |
| unknown1=0, |
| flags=3, |
| separation=850, |
| paragraphs=2), child_model['content']) |
| self.assertEqual(b'', child_context['stream'].read()) |
|
|
|
|
| class ShapeComponentTest(TestBase): |
|
|
| hwp5file_name = 'textbox.hwp' |
|
|
| @cached_property |
| def control_gso_record(self): |
| return self.bodytext.section(0).record(12) |
|
|
| @cached_property |
| def shapecomponent_record(self): |
| return self.bodytext.section(0).record(19) |
|
|
| @cached_property |
| def textbox_paragraph_list_record(self): |
| return self.bodytext.section(0).record(20) |
|
|
| def test_parse_shapecomponent_textbox_paragraph_list(self): |
| record = self.shapecomponent_record |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| model['type'] = ShapeComponent |
|
|
| child_record = self.textbox_paragraph_list_record |
| child_context = init_record_parsing_context(testcontext, |
| child_record) |
| child_context['parent'] = context, model |
| child_model = child_record |
| parse_model(child_context, child_model) |
| self.assertEqual(TextboxParagraphList, child_model['type']) |
| self.assertEqual(dict(listflags=32, |
| padding=dict(top=283, right=283, bottom=283, |
| left=283), |
| unknown1=0, |
| maxwidth=11763, |
| paragraphs=1), child_model['content']) |
| self.assertEqual(b'', child_context['stream'].read()) |
|
|
| def test_parse(self): |
|
|
| |
|
|
| |
| parent_model = dict(type=GShapeObjectControl) |
|
|
| record = self.shapecomponent_record |
| context = init_record_parsing_context(testcontext, record) |
| context['parent'] = dict(), parent_model |
| model = record |
| parse_model(context, model) |
|
|
| self.assertEqual(model['type'], ShapeComponent) |
| self.assertTrue('chid0' in model['content']) |
|
|
| |
| |
|
|
| def test_rect_fill(self): |
| self.hwp5file_name = 'shapecomponent-rect-fill.hwp' |
|
|
| section = self.hwp5file_bin.bodytext.section(0) |
| shapecomps = (model for model in section.models() |
| if model['type'] is ShapeComponent) |
| shapecomps = list(shapecomps) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertFalse(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertFalse(shapecomp['fill_flags'].fill_gradation) |
| self.assertFalse(shapecomp['fill_flags'].fill_image) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertTrue(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertFalse(shapecomp['fill_flags'].fill_gradation) |
| self.assertFalse(shapecomp['fill_flags'].fill_image) |
| self.assertEqual(dict(background_color=0xff7f3f, |
| pattern_color=0, |
| pattern_type_flags=0xffffffff), |
| shapecomp['fill_colorpattern']) |
| self.assertEqual(None, shapecomp.get('fill_gradation')) |
| self.assertEqual(None, shapecomp.get('fill_image')) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertFalse(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertTrue(shapecomp['fill_flags'].fill_gradation) |
| self.assertFalse(shapecomp['fill_flags'].fill_image) |
| self.assertEqual(None, shapecomp.get('fill_colorpattern')) |
| self.assertEqual(dict(type=1, shear=90, |
| center=dict(x=0, y=0), |
| colors=[0xff7f3f, 0], |
| blur=50), shapecomp['fill_gradation']) |
| self.assertEqual(None, shapecomp.get('fill_image')) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertFalse(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertFalse(shapecomp['fill_flags'].fill_gradation) |
| self.assertTrue(shapecomp['fill_flags'].fill_image) |
| self.assertEqual(None, shapecomp.get('fill_colorpattern')) |
| self.assertEqual(None, shapecomp.get('fill_gradation')) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| shapecomp['fill_image']) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertTrue(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertFalse(shapecomp['fill_flags'].fill_gradation) |
| self.assertTrue(shapecomp['fill_flags'].fill_image) |
| self.assertEqual(dict(background_color=0xff7f3f, |
| pattern_color=0, |
| pattern_type_flags=0xffffffff), |
| shapecomp['fill_colorpattern']) |
| self.assertEqual(None, shapecomp.get('fill_gradation')) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| shapecomp['fill_image']) |
|
|
| shapecomp = shapecomps.pop(0)['content'] |
| self.assertFalse(shapecomp['fill_flags'].fill_colorpattern) |
| self.assertTrue(shapecomp['fill_flags'].fill_gradation) |
| self.assertTrue(shapecomp['fill_flags'].fill_image) |
| self.assertEqual(None, shapecomp.get('fill_colorpattern')) |
| self.assertEqual(dict(type=1, shear=90, |
| center=dict(x=0, y=0), |
| colors=[0xff7f3f, 0], |
| blur=50), shapecomp['fill_gradation']) |
| self.assertEqual(dict(flags=5, bindata_id=1, effect=0, brightness=0, |
| contrast=0), |
| shapecomp['fill_image']) |
|
|
| def test_colorpattern_gradation(self): |
| |
| records = \ |
| [{'level': 1, |
| 'payload': b' osg\x10bj\x04\x00\x00\x00\x00\x0c\x00\x00\x004\xbc\x00\x00l\x06\x01\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00G\xechf\x00\x00\x00\x00', |
| 'seqno': 12, |
| 'size': 44, |
| 'tagid': 71, |
| 'tagname': 'HWPTAG_CTRL_HEADER'}, |
| {'level': 2, |
| 'payload': b'noc$noc$\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x004\xbc\x00\x00l\x06\x01\x004\xbc\x00\x00l\x06\x01\x00\x00\x00\x03\x00\x00\x00w\x00WPS \xb9\xae\x01\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00lop$noc$', |
| 'seqno': 13, |
| 'size': 206, |
| 'tagid': 76, |
| 'tagname': 'HWPTAG_SHAPE_COMPONENT'}, |
| {'level': 3, |
| 'payload': b'lop$\x00\x00\x00\x00\xcc\x1b\x00\x00\x01\x00\x01\x004\xbc\x00\x00\xa0\xea\x00\x004\xbc\x00\x00\xa0\xea\x00\x00\x00\x00\x00\x00\x10\x00\x1a^\x00\x00Pu\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\xcc\xbb@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe4\x00\x00\xc8\x00\x00\x00\x01\x00\x00\xd1\x00\x00\x00\x00\x00\x00\x00\x00\x00', |
| 'seqno': 14, |
| 'size': 309, |
| 'tagid': 76, |
| 'tagname': 'HWPTAG_SHAPE_COMPONENT'}, |
| {'level': 4, |
| 'payload': b'\x06\x00\x00\x00`,\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9c\xea\x00\x000\xbc\x00\x00\x9c\xea\x00\x000\xbc\x00\x00\x00\x00\x00\x00\xac\x8a\x00\x00\x00\x00\x00\x00', |
| 'seqno': 15, |
| 'size': 52, |
| 'tagid': 82, |
| 'tagname': 'HWPTAG_SHAPE_COMPONENT_POLYGON'}, |
| {'level': 3, |
| 'payload': b'noc$\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00tb\x00\x00\xf4-\x00\x00tb\x00\x00\xf4-\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00cer$cer$cer$cer$cer$', |
| 'seqno': 16, |
| 'size': 310, |
| 'tagid': 76, |
| 'tagname': 'HWPTAG_SHAPE_COMPONENT'}, |
| {'level': 4, |
| 'payload': b'cer$\xd8,\x00\x00\x00\x00\x00\x00\x02\x00\x01\x00\x04\x0f\x00\x00\xf4-\x00\x00\x04\x0f\x00\x00\xf4-\x00\x00\x00\x00\x00\x01\x00\x00\x82\x07\x00\x00\xfa\x16\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00l\xc6@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\xc8\x00\x00\x00\x00\x00\x00\xd1\x00\x05\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x01\xb4\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x002\x00\x00\x00\x02\x00\x00\x00\x00\xfc\x00\x00\xe0\xfc\xc8\x00\x01\x00\x00\x002', |
| 'seqno': 17, |
| 'size': 447, |
| 'tagid': 76, |
| 'tagname': 'HWPTAG_SHAPE_COMPONENT'}] |
|
|
| context = dict(version=(5, 0, 0, 5)) |
| models = parse_models(context, records) |
| models = list(models) |
| self.assertEqual(1280, models[-1]['content']['fill_flags']) |
| colorpattern = models[-1]['content']['fill_colorpattern'] |
| gradation = models[-1]['content']['fill_gradation'] |
| self.assertEqual(32768, colorpattern['background_color']) |
| self.assertEqual(0, colorpattern['pattern_color']) |
| self.assertEqual(0xffffffff, colorpattern['pattern_type_flags']) |
|
|
| self.assertEqual(50, gradation['blur']) |
| self.assertEqual(dict(x=0, y=100), gradation['center']) |
| self.assertEqual([64512, 13171936], gradation['colors']) |
| self.assertEqual(180, gradation['shear']) |
| self.assertEqual(1, gradation['type']) |
| self.assertEqual(1, models[-1]['content']['fill_shape']) |
| self.assertEqual(50, models[-1]['content']['fill_blur_center']) |
|
|
| def test_colorpattern_gradation_5017(self): |
| fixturename = '5017-shapecomponent-with-colorpattern-and-gradation.bin' |
| f = self.open_fixture(fixturename, 'rb') |
| try: |
| records = list(read_records(f)) |
| finally: |
| f.close() |
|
|
| context = dict(version=(5, 0, 1, 7)) |
| models = parse_models(context, records) |
| models = list(models) |
| self.assertEqual(1280, models[-1]['content']['fill_flags']) |
| colorpattern = models[-1]['content']['fill_colorpattern'] |
| gradation = models[-1]['content']['fill_gradation'] |
| self.assertEqual(32768, colorpattern['background_color']) |
| self.assertEqual(0, colorpattern['pattern_color']) |
| self.assertEqual(0xffffffff, colorpattern['pattern_type_flags']) |
|
|
| self.assertEqual(50, gradation['blur']) |
| self.assertEqual(dict(x=0, y=100), gradation['center']) |
| self.assertEqual([64512, 13171936], gradation['colors']) |
| self.assertEqual(180, gradation['shear']) |
| self.assertEqual(1, gradation['type']) |
| self.assertEqual(1, models[-1]['content']['fill_shape']) |
| self.assertEqual(50, models[-1]['content']['fill_blur_center']) |
|
|
|
|
| class HeaderFooterTest(TestBase): |
|
|
| hwp5file_name = 'headerfooter.hwp' |
|
|
| @cached_property |
| def header_record(self): |
| return self.bodytext.section(0).record(16) |
|
|
| @cached_property |
| def header_paragraph_list_record(self): |
| return self.bodytext.section(0).record(17) |
|
|
| def test_parse_child(self): |
| record = self.header_record |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| child_record = self.header_paragraph_list_record |
| child_context = init_record_parsing_context(testcontext, |
| child_record) |
| child_context['parent'] = context, model |
| child_model = child_record |
| parse_model(child_context, child_model) |
| self.assertEqual(HeaderParagraphList, child_model['type']) |
| self.assertEqual(dict(textrefsbitmap=0, |
| numberrefsbitmap=0, |
| height=4252, |
| listflags=0, |
| width=42520, |
| unknown1=0, |
| paragraphs=1), child_model['content']) |
| |
| |
|
|
|
|
| class ListHeaderTest(TestCase): |
| ctx = TestContext() |
| record_bytes = (b'H\x08`\x02\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00' |
| b'\x01\x00\x01\x00\x03O\x00\x00\x1a\x01\x00\x00\x8d\x00' |
| b'\x8d\x00\x8d\x00\x8d\x00\x01\x00\x03O\x00\x00') |
| stream = BytesIO(record_bytes) |
|
|
| def testParse(self): |
| record = next(read_records(self.stream)) |
| context = init_record_parsing_context(testcontext, record) |
| model = record |
| parse_model(context, model) |
|
|
| self.assertEqual(ListHeader, model['type']) |
| self.assertEqual(1, model['content']['paragraphs']) |
| self.assertEqual(0x20, model['content']['listflags']) |
| self.assertEqual(0, model['content']['unknown1']) |
| self.assertEqual(8, context['stream'].tell()) |
|
|
|
|
| class TableBodyTest(TestCase): |
| ctx = TestContext(version=(5, 0, 1, 7)) |
| stream = BytesIO(b'M\x08\xa0\x01\x06\x00\x00\x04\x02\x00\x02\x00\x00\x00' |
| b'\x8d\x00\x8d\x00\x8d\x00\x8d\x00\x02\x00\x02\x00\x01' |
| b'\x00\x00\x00') |
|
|
| def test_parse_model(self): |
| record = next(read_records(self.stream)) |
| context = init_record_parsing_context(self.ctx, record) |
| model = record |
|
|
| parse_model(context, model) |
| model_type = model['type'] |
| model_content = model['content'] |
|
|
| self.assertEqual(TableBody, model_type) |
| self.assertEqual(dict(left=141, right=141, top=141, bottom=141), |
| model_content['padding']) |
| self.assertEqual(0x4000006, model_content['flags']) |
| self.assertEqual(2, model_content['cols']) |
| self.assertEqual(2, model_content['rows']) |
| self.assertEqual(1, model_content['borderfill_id']) |
| self.assertEqual([2, 2], model_content['rowcols']) |
| self.assertEqual(0, model_content['cellspacing']) |
| self.assertEqual([], model_content['validZones']) |
|
|
|
|
| class Pass2Test(TestCase): |
| ctx = TestContext() |
|
|
| def test_pass2_events(self): |
|
|
| def items(): |
| yield Record(HWPTAG_BEGIN + 4, 0, ''), |
| yield Record(HWPTAG_BEGIN + 3, 1, ''), |
| yield Record(HWPTAG_BEGIN + 2, 0, ''), |
| yield Record(HWPTAG_BEGIN + 1, 0, ''), |
| items = list(item for item in items()) |
| leveld_items = zip([0, 1, 0, 0], items) |
|
|
| events = list(prefix_event(leveld_items)) |
|
|
| def expected(): |
| yield STARTEVENT, items[0] |
| yield STARTEVENT, items[1] |
| yield ENDEVENT, items[1] |
| yield ENDEVENT, items[0] |
| yield STARTEVENT, items[2] |
| yield ENDEVENT, items[2] |
| yield STARTEVENT, items[3] |
| yield ENDEVENT, items[3] |
| expected = list(expected()) |
| self.assertEqual(expected, events) |
|
|
|
|
| class LineSegTest(TestCase): |
| def testDecode(self): |
| data = ('00000000481e0000e8030000e80300005203000058020000dc0500003ca00' |
| '000000006003300000088240000e8030000e80300005203000058020000dc' |
| '0500003ca000000000060067000000c82a0000e8030000e80300005203000' |
| '058020000dc0500003ca0000000000600') |
| data = binascii.a2b_hex(data) |
| lines = list(ParaLineSegList.decode(dict(), data)) |
| self.assertEqual(0, lines[0]['chpos']) |
| self.assertEqual(51, lines[1]['chpos']) |
| self.assertEqual(103, lines[2]['chpos']) |
|
|
|
|
| class TableCaptionCellTest(TestCase): |
| ctx = TestContext(version=(5, 0, 1, 7)) |
| records_bytes = (b'G\x04\xc0\x02 lbt\x10#*(\x00\x00\x00\x00\x00\x00\x00\x00' |
| b'\x06\x9e\x00\x00\x04\n\x00\x00\x03\x00\x00\x00\x1b\x01R' |
| b'\x037\x02n\x04\n^\xc0V\x00\x00\x00\x00H\x08`\x01\x02\x00' |
| b'\x00\x00\x00\x00\x00\x00\x03\x00\x00\x008!\x00\x00R\x03' |
| b'\x06\x9e\x00\x00M\x08\xa0\x01\x06\x00\x00\x04\x02\x00' |
| b'\x02\x00\x00\x00\x8d\x00\x8d\x00\x8d\x00\x8d\x00\x02\x00' |
| b'\x02\x00\x01\x00\x00\x00H\x08`\x02\x01\x00\x00\x00 \x00' |
| b'\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x03O\x00\x00' |
| b'\x1a\x01\x00\x00\x8d\x00\x8d\x00\x8d\x00\x8d\x00\x01\x00' |
| b'\x03O\x00\x00') |
|
|
| def testParsePass1(self): |
| stream = BytesIO(self.records_bytes) |
| records = list(read_records(stream)) |
| result = list(parse_models_intern(self.ctx, records)) |
|
|
| tablecaption = result[1] |
| context, model = tablecaption |
| model_type = model['type'] |
| model_content = model['content'] |
| stream = context['stream'] |
|
|
| self.assertEqual(TableCaption, model_type) |
| self.assertEqual(22, stream.tell()) |
| |
| self.assertEqual(2, model_content['paragraphs']) |
| self.assertEqual(0x0, model_content['listflags']) |
| self.assertEqual(0, model_content['unknown1']) |
| |
| self.assertEqual(3, model_content['flags']) |
| self.assertEqual(8504, model_content['width']) |
| self.assertEqual(850, model_content['separation']) |
| self.assertEqual(40454, model_content['max_width']) |
|
|
| tablecell = result[3] |
| context, model = tablecell |
| model_type = model['type'] |
| model_content = model['content'] |
| stream = context['stream'] |
| self.assertEqual(TableCell, model_type) |
| self.assertEqual(38, stream.tell()) |
| |
| self.assertEqual(1, model_content['paragraphs']) |
| self.assertEqual(0x20, model_content['listflags']) |
| self.assertEqual(0, model_content['unknown1']) |
| |
| self.assertEqual(0, model_content['col']) |
| self.assertEqual(0, model_content['row']) |
| self.assertEqual(1, model_content['colspan']) |
| self.assertEqual(1, model_content['rowspan']) |
| self.assertEqual(0x4f03, model_content['width']) |
| self.assertEqual(0x11a, model_content['height']) |
| self.assertEqual(dict(left=141, right=141, top=141, bottom=141), |
| model_content['padding']) |
| self.assertEqual(1, model_content['borderfill_id'],) |
| self.assertEqual(0x4f03, model_content['unknown_width']) |
|
|
|
|
| class TestRecordModel(TestCase): |
| def test_assign_enum_flags_name(self): |
|
|
| class FooRecord(RecordModel): |
| Bar = Flags(UINT32) |
| Baz = Enum() |
| self.assertEqual('Bar', FooRecord.Bar.__name__) |
| self.assertEqual('Baz', FooRecord.Baz.__name__) |
|
|
|
|
| class TestControlType(TestCase): |
| def test_ControlType(self): |
|
|
| class FooControl(Control): |
| chid = 'foo!' |
| try: |
| class Foo2Control(Control): |
| chid = 'foo!' |
| except Exception: |
| pass |
| else: |
| assert False, 'Exception expected' |
|
|
|
|
| class TestControlChar(TestBase): |
|
|
| def test_decode(self): |
| paratext_record = self.hwp5file.bodytext.section(0).record(1) |
| payload = paratext_record['payload'] |
| controlchar = ControlChar.decode(payload[0:16]) |
| self.assertEqual(dict(code=ord(ControlChar.SECTION_COLUMN_DEF), |
| chid='secd', |
| param=b'\x00' * 8), controlchar) |
|
|
| def test_find(self): |
| bytes = b'\x41\x00' |
| self.assertEqual((2, 2), ControlChar.find(bytes, 0)) |
|
|
| def test_tab(self): |
| self.hwp5file_name = 'tabdef.hwp' |
| models = self.hwp5file.bodytext.section(0).models() |
| paratexts = list(model for model in models |
| if model['type'] is ParaText) |
|
|
| def paratext_tabs(paratext): |
| for range, chunk in paratext['content']['chunks']: |
| if isinstance(chunk, dict): |
| if unichr(chunk['code']) == ControlChar.TAB: |
| yield chunk |
| self.assertEqual(set(['code', 'param']), |
| set(next(paratext_tabs(paratexts[0])).keys())) |
|
|
| def paratext_tab_params(paratext): |
| for tab in paratext_tabs(paratext): |
| yield tab['param'] |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(4000, 1)] * 3, |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(2000, 1), (1360, 1), (1360, 1)], |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(2328, 2)] * 3, |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(2646, 3), (2292, 3), (2292, 3)], |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(2104, 4)] * 3, |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(4000, 1), (3360, 1), (3360, 1)], |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(4000, 1), (3328, 1)], |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
| tabs = list(paratext_tab_params(paratexts.pop(0))) |
| self.assertEqual([(4000, 1), (3672, 1), (33864, 2)], |
| list((tab['width'], tab['unknown1']) |
| for tab in tabs)) |
|
|
|
|
| class TestFootnoteShape(TestBase): |
|
|
| def test_footnote_shape(self): |
| path = get_fixture_path('footnote-endnote.hwp') |
| hwp5file = Hwp5File(path) |
|
|
| models = hwp5file.bodytext.section(0).models() |
| models = list(models) |
| fnshape = models[6] |
| self.assertEqual(850, fnshape['content']['splitter_margin_top']) |
| self.assertEqual(567, fnshape['content']['splitter_margin_bottom']) |
|
|
|
|
| class TestControlData(TestBase): |
| def test_parse(self): |
| |
| record = \ |
| {'level': 2, |
| 'payload': b'\x1b\x02\x01\x00\x00\x00\x00@\x01\x00\x03\x00X\xc7H\xc5\x85\xba', |
| 'seqno': 27, |
| 'size': 18, |
| 'tagid': 87, |
| 'tagname': 'HWPTAG_CTRL_DATA'} |
| context = init_record_parsing_context(dict(), record) |
| model = record |
| parse_model(context, model) |
| self.assertEqual(ControlData, model['type']) |
| self.assertEqual(dict(), model['content']) |
|
|
|
|
| class TestModelJson(TestBase): |
| def test_model_to_json(self): |
| model = self.hwp5file.docinfo.model(0) |
| json_string = model_to_json(model) |
|
|
| jsonobject = json.loads(json_string) |
| self.assertEqual('DocumentProperties', jsonobject['type']) |
|
|
| def test_model_to_json_should_not_modify_input(self): |
| model = self.hwp5file.docinfo.model(0) |
| model_to_json(model, indent=2, sort_keys=True) |
| self.assertFalse(isinstance(model['type'], basestring)) |
|
|
| def test_model_to_json_with_controlchar(self): |
| model = self.hwp5file.bodytext.section(0).model(1) |
| json_string = model_to_json(model) |
|
|
| jsonobject = json.loads(json_string) |
| self.assertEqual('ParaText', jsonobject['type']) |
| self.assertEqual([[0, 8], |
| dict(code=2, param='\x00' * 8, chid='secd')], |
| jsonobject['content']['chunks'][0]) |
|
|
| def test_model_to_json_with_unparsed(self): |
|
|
| model = dict(type=RecordModel, content=[], payload=b'\x00\x01\x02\x03', |
| unparsed=b'\xff\xfe\xfd\xfc') |
| json_string = model_to_json(model) |
|
|
| jsonobject = json.loads(json_string) |
| self.assertEqual(['ff fe fd fc'], jsonobject['unparsed']) |
|
|
| def test_generate_models_json_array(self): |
| models_json = self.hwp5file.bodytext.section(0).models_json() |
| gen = models_json.generate() |
|
|
| json_array = json.loads(''.join(gen)) |
| self.assertEqual(128, len(json_array)) |
|
|
|
|
| class TestModelStream(TestBase): |
| @cached_property |
| def docinfo(self): |
| return ModelStream(self.hwp5file_rec['DocInfo'], |
| self.hwp5file_rec.header.version) |
|
|
| def test_models(self): |
| self.assertEqual(67, len(list(self.docinfo.models()))) |
|
|
| def test_models_treegrouped(self): |
| section = self.bodytext.section(0) |
| for idx, paragraph_models in enumerate(section.models_treegrouped()): |
| paragraph_models = list(paragraph_models) |
| leader = paragraph_models[0] |
| |
| self.assertEqual(Paragraph, leader['type']) |
| |
| self.assertEqual(0, leader['level']) |
| |
|
|
| def test_model(self): |
| model = self.docinfo.model(0) |
| self.assertEqual(0, model['seqno']) |
|
|
| model = self.docinfo.model(10) |
| self.assertEqual(10, model['seqno']) |
|
|
| def test_models_json_open(self): |
| f = self.docinfo.models_json().open() |
| f = codecs.getreader('utf-8')(f) |
| try: |
| self.assertEqual(67, len(json.load(f))) |
| finally: |
| f.close() |
|
|