def setUp(self): self.wrapper_state = WrapperState({ 'header-one': { 'element': 'h1' }, 'unstyled': { 'element': 'div' }, })
def render(self, content_state): """ Starts the export process on a given piece of content state. """ self.wrapper_state = WrapperState(self.block_map) self.style_state = StyleState(self.style_map) entity_map = content_state.get('entityMap', {}) for block in content_state.get('blocks', []): self.render_block(block, entity_map) self.wrapper_state.clean_up() return self.wrapper_state.to_string()
def setUp(self): DOM.use(DOM.STRING) self.wrapper_state = WrapperState({ 'header-one': 'h1', 'unstyled': 'div', 'atomic': lambda props: props['children'], 'ignore': None, 'blockquote': blockquote, 'ordered-list-item': { 'element': list_item, 'wrapper': ordered_list }, })
def render(self, content_state=None): """ Starts the export process on a given piece of content state. """ if content_state is None: content_state = {} blocks = content_state.get('blocks', []) wrapper_state = WrapperState(self.block_options, blocks) document = DOM.create_element() entity_map = content_state.get('entityMap', {}) min_depth = 0 for block in blocks: # Assume a depth of 0 if it's not specified, like Draft.js would. depth = block['depth'] if 'depth' in block else 0 elt = self.render_block(block, entity_map, wrapper_state) if depth > min_depth: min_depth = depth # At level 0, append the element to the document. if depth == 0: DOM.append_child(document, elt) # If there is no block at depth 0, we need to add the wrapper that contains the whole tree to the document. if min_depth > 0 and wrapper_state.stack.length() != 0: DOM.append_child(document, wrapper_state.stack.tail().elt) return DOM.render(document)
def render(self, content_state=None): """ Starts the export process on a given piece of content state. """ if content_state is None: content_state = {} wrapper_state = WrapperState(self.block_map) document = DOM.create_element() entity_map = content_state.get('entityMap', {}) min_depth = 0 for block in content_state.get('blocks', []): depth = block['depth'] elt = self.render_block(block, entity_map, wrapper_state) if depth > min_depth: min_depth = depth # At level 0, append the element to the document. if depth == 0: DOM.append_child(document, elt) # If there is no block at depth 0, we need to add the wrapper that contains the whole tree to the document. if min_depth > 0 and wrapper_state.stack.length() != 0: DOM.append_child(document, wrapper_state.stack.tail().elt) return DOM.render(document)
def render_block(self, block: Block, entity_map: EntityMap, wrapper_state: WrapperState) -> Element: has_styles = "inlineStyleRanges" in block and block["inlineStyleRanges"] has_entities = "entityRanges" in block and block["entityRanges"] has_decorators = should_render_decorators(self.composite_decorators, block["text"]) if has_styles or has_entities: content = DOM.create_element() entity_state = EntityState(self.entity_options, entity_map) style_state = StyleState( self.style_options) if has_styles else None for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) if style_state: style_state.apply(command) # Decorators are not rendered inside entities. if has_decorators and entity_state.has_no_entity(): decorated_node = render_decorators( self.composite_decorators, text, block, wrapper_state.blocks, ) else: decorated_node = text if style_state: styled_node = style_state.render_styles( decorated_node, block, wrapper_state.blocks) else: styled_node = decorated_node entity_node = entity_state.render_entities( styled_node, block, wrapper_state.blocks) if entity_node is not None: DOM.append_child(content, entity_node) # Check whether there actually are two different nodes, confirming we are not inserting an upcoming entity. if (styled_node != entity_node and entity_state.has_no_entity()): DOM.append_child(content, styled_node) # Fast track for blocks which do not contain styles nor entities, which is very common. elif has_decorators: content = render_decorators( self.composite_decorators, block["text"], block, wrapper_state.blocks, ) else: content = block["text"] return wrapper_state.element_for(block, content)
def setUp(self): DOM.use(DOM.STRING) self.wrapper_state = WrapperState( Options.map_blocks( { "header-one": "h1", "unstyled": "div", "atomic": lambda props: props["children"], "ignore": None, "blockquote": blockquote, "ordered-list-item": { "element": list_item, "wrapper": ordered_list, }, } ), [], )
def test_element_for_data(self): blocks = [ { 'key': '5s7g9', 'text': 'test', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [], }, ] def unstyled(props): self.assertEqual(props['blocks'], blocks) self.assertEqual(props['block'], blocks[0]) WrapperState(Options.map_blocks({'unstyled': unstyled}), blocks).element_for(blocks[0], 'test')
def test_element_for_data(self): blocks = [ { "key": "5s7g9", "text": "test", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], } ] def unstyled(props): self.assertEqual(props["blocks"], blocks) self.assertEqual(props["block"], blocks[0]) WrapperState( Options.map_blocks({"unstyled": unstyled}), blocks ).element_for(blocks[0], "test")
class HTML: """ Entry point of the exporter. Combines entity, wrapper and style state to generate the right HTML nodes. """ def __init__(self, config=None): if config is None: config = {} self.entity_decorators = config.get('entity_decorators', {}) self.block_map = config.get('block_map', BLOCK_MAP) self.style_map = config.get('style_map', STYLE_MAP) def render(self, content_state): """ Starts the export process on a given piece of content state. """ self.wrapper_state = WrapperState(self.block_map) self.style_state = StyleState(self.style_map) entity_map = content_state.get('entityMap', {}) for block in content_state.get('blocks', []): self.render_block(block, entity_map) self.wrapper_state.clean_up() return self.wrapper_state.to_string() def render_block(self, block, entity_map): element = self.wrapper_state.element_for(block) entity_state = EntityState(self.entity_decorators, entity_map) for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) self.style_state.apply(command) style_node = self.style_state.create_node(text) entity_state.render_entitities(element, style_node) def build_command_groups(self, block): """ Creates block modification commands, grouped by start index, with the text to apply them on. """ text = block.get('text') commands = self.build_commands(block) # Tried using itertools.tee but for some reason that failed. Oh well. grouped = Command.grouped_by_index(commands) listed = list(Command.grouped_by_index(commands)) sliced = [] i = 0 for start_index, commands in grouped: next_group = listed[i + 1] if i + 1 < len(listed) else False stop_index = next_group[0] if next_group else 0 sliced.append((text[start_index:stop_index], list(commands))) i += 1 return sliced def build_commands(self, block): """ Build all of the manipulation commands for a given block. - One pair to set the text. - Multiple pairs for styles. - Multiple pairs for entities. """ text_commands = Command.start_stop('text', 0, len(block.get('text'))) style_commands = self.build_style_commands(block) entity_commands = self.build_entity_commands(block) return text_commands + style_commands + entity_commands def build_style_commands(self, block): ranges = block.get('inlineStyleRanges', []) return Command.from_ranges(ranges, 'inline_style', 'style') def build_entity_commands(self, block): ranges = block.get('entityRanges', []) return Command.from_ranges(ranges, 'entity', 'key')
class TestWrapperState(unittest.TestCase): def setUp(self): self.wrapper_state = WrapperState({ 'header-one': { 'element': 'h1' }, 'unstyled': { 'element': 'div' }, }) def test_init(self): self.assertIsInstance(self.wrapper_state, WrapperState) def test_element_for_text(self): self.assertEqual( DOM.get_text_content( self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] })), '') def test_element_for_tag(self): self.assertEqual( DOM.get_tag_name( self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] })), 'h1') def test_element_for_raises(self): with self.assertRaises(BlockException): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-two', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) def test_to_string_empty(self): self.assertEqual(self.wrapper_state.to_string(), '') def test_to_string_elts(self): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) self.assertEqual(self.wrapper_state.to_string(), '<h1></h1>') def test_str_empty(self): self.assertEqual(str(self.wrapper_state), '<WrapperState: >') def test_str_elts(self): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) self.assertEqual(str(self.wrapper_state), '<WrapperState: <h1></h1>>') def test_map_element_options_full(self): self.assertEqual(Options.map([ 'ul', { 'className': 'bullet-list' }, ]), [ 'ul', { 'className': 'bullet-list' }, ]) def test_map_element_options_half(self): self.assertEqual(Options.map([ 'ul', ]), [ 'ul', {}, ]) def test_map_element_options_simplest(self): self.assertEqual(Options.map('ul'), [ 'ul', {}, ])
def setUp(self): self.wrapper_state = WrapperState(block_map)
class TestWrapperState(unittest.TestCase): def setUp(self): self.wrapper_state = WrapperState(block_map) def test_init(self): self.assertIsInstance(self.wrapper_state, WrapperState) def test_element_for_text(self): self.assertEqual(self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }).text, None) def test_element_for_tag(self): self.assertEqual(self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }).tag, 'h1') def test_element_for_raises(self): with self.assertRaises(BlockException): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-two', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) def test_to_string_empty(self): self.assertEqual(self.wrapper_state.to_string(), '') def test_to_string_elts(self): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) self.assertEqual(self.wrapper_state.to_string(), '<h1></h1>') def test_str_empty(self): self.assertEqual(str(self.wrapper_state), '<WrapperState: >') def test_str_elts(self): self.wrapper_state.element_for({ 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }) self.assertEqual(str(self.wrapper_state), '<WrapperState: <h1></h1>>')
def __init__(self, config={}): self.entity_decorators = config.get('entity_decorators', {}) self.wrapper_state = WrapperState(config.get('block_map', BLOCK_MAP)) self.style_state = StyleState(config.get('style_map', STYLE_MAP))
class TestWrapperState(unittest.TestCase): def setUp(self): DOM.use(DOM.STRING) self.wrapper_state = WrapperState( Options.map_blocks( { "header-one": "h1", "unstyled": "div", "atomic": lambda props: props["children"], "ignore": None, "blockquote": blockquote, "ordered-list-item": { "element": list_item, "wrapper": ordered_list, }, } ), [], ) def test_init(self): self.assertIsInstance(self.wrapper_state, WrapperState) def test_element_for_data(self): blocks = [ { "key": "5s7g9", "text": "test", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], } ] def unstyled(props): self.assertEqual(props["blocks"], blocks) self.assertEqual(props["block"], blocks[0]) WrapperState( Options.map_blocks({"unstyled": unstyled}), blocks ).element_for(blocks[0], "test") def test_element_for_simple_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Header", "type": "header-one", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], }, "Header", ) ), "<h1>Header</h1>", ) def test_element_for_element_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Paragraph", "type": "unstyled", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], }, DOM.create_element("strong", {}, "Paragraph"), ) ), "<div><strong>Paragraph</strong></div>", ) def test_element_for_dismiss_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Paragraph", "type": "ignore", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], }, DOM.create_element("img", {"src": "/example.png"}), ) ), "<fragment></fragment>", ) def test_element_for_no_block(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Paragraph", "type": "atomic", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], }, DOM.create_element("img", {"src": "/example.png"}), ) ), '<img src="/example.png"/>', ) def test_element_for_component(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Paragraph", "type": "blockquote", "depth": 0, "data": {"cite": "http://example.com/"}, "inlineStyleRanges": [], "entityRanges": [], }, "Test", ) ), '<blockquote cite="http://example.com/">Test</blockquote>', ) def test_element_for_component_wrapper(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Test", "type": "ordered-list-item", "depth": 0, "data": {}, "inlineStyleRanges": [], "entityRanges": [], }, "Test", ) ), '<ol class="list--depth-0"><li class="list-item--depth-0">Test</li></ol>', ) def test_str_elts(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { "key": "5s7g9", "text": "Header", "type": "header-one", "depth": 0, "inlineStyleRanges": [], "entityRanges": [], }, "", ) ), "<h1></h1>", ) def test_str(self): self.assertEqual(str(self.wrapper_state), "<WrapperState: []>")
class TestWrapperState(unittest.TestCase): def setUp(self): DOM.use(DOM.STRING) self.wrapper_state = WrapperState( { 'header-one': 'h1', 'unstyled': 'div', 'atomic': lambda props: props['children'], 'ignore': None, 'blockquote': blockquote, 'ordered-list-item': { 'element': list_item, 'wrapper': ordered_list }, }, []) def test_init(self): self.assertIsInstance(self.wrapper_state, WrapperState) def test_element_for_data(self): blocks = [ { 'key': '5s7g9', 'text': 'test', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [], }, ] def unstyled(props): self.assertEqual(props['blocks'], blocks) self.assertEqual(props['block'], blocks[0]) WrapperState({ 'unstyled': unstyled }, blocks).element_for(blocks[0], 'test') def test_element_for_simple_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }, 'Header')), '<h1>Header</h1>') def test_element_for_element_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Paragraph', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }, DOM.create_element('strong', {}, 'Paragraph'))), '<div><strong>Paragraph</strong></div>') def test_element_for_dismiss_content(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Paragraph', 'type': 'ignore', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }, DOM.create_element('img', {'src': '/example.png'}))), '<fragment></fragment>') def test_element_for_no_block(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Paragraph', 'type': 'atomic', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }, DOM.create_element('img', {'src': '/example.png'}))), '<img src="/example.png"/>') def test_element_for_component(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Paragraph', 'type': 'blockquote', 'depth': 0, 'data': { 'cite': 'http://example.com/', }, 'inlineStyleRanges': [], 'entityRanges': [] }, 'Test')), '<blockquote cite="http://example.com/">Test</blockquote>') def test_element_for_component_wrapper(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Test', 'type': 'ordered-list-item', 'depth': 0, 'data': {}, 'inlineStyleRanges': [], 'entityRanges': [] }, 'Test')), '<ol class="list--depth-0"><li class="list-item--depth-0">Test</li></ol>' ) def test_str_elts(self): self.assertEqual( DOM.render_debug( self.wrapper_state.element_for( { 'key': '5s7g9', 'text': 'Header', 'type': 'header-one', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [] }, '')), '<h1></h1>') def test_str(self): self.assertEqual(str(self.wrapper_state), '<WrapperState: []>')