def render_entities(self, style_node): # We have a complete (start, stop) entity to render. if self.completed_entity is not None: entity_details = self.get_entity_details(self.completed_entity) opts = Options.for_entity(self.entity_decorators, entity_details['type']) props = entity_details['data'].copy() props['entity'] = { 'type': entity_details['type'], } if len(self.element_stack) == 1: children = self.element_stack[0] else: children = DOM.create_element() for n in self.element_stack: DOM.append_child(children, n) self.completed_entity = None self.element_stack = [] # Is there still another entity? (adjacent) if so add the current style_node for it. if self.has_entity(): self.element_stack.append(style_node) return DOM.create_element(opts.element, props, children) if self.has_entity(): self.element_stack.append(style_node) return None return style_node
def add_node(self, element, text): if self.is_unstyled(): child = DOM.create_text_node(text) DOM.append_child(element, child) else: tags = self.get_style_tags() child = element # Nest the tags. # Set the text and style attribute (if any) on the deepest node. for tag in tags: new_child = DOM.create_element(tag) DOM.append_child(child, new_child) child = new_child style_value = self.get_style_value() if style_value: DOM.set_attribute(child, 'style', style_value) class_value = self.get_class_value() if class_value: DOM.set_attribute(child, 'class', class_value) DOM.set_text_content(child, text) return child
def render_entities(self, style_node): if self.completed_entity is not None: entity_details = self.get_entity_details(self.completed_entity) opts = Options.for_entity(self.entity_decorators, entity_details['type']) props = entity_details['data'].copy() props['entity'] = { 'type': entity_details['type'], } nodes = DOM.create_element() for n in self.element_stack: DOM.append_child(nodes, n) elt = DOM.create_element(opts.element, props, nodes) self.completed_entity = None self.element_stack = [] elif self.has_no_entity(): elt = style_node else: self.element_stack.append(style_node) elt = None return elt
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 = {} blocks = content_state.get('blocks', []) wrapper_state = WrapperState(self.block_map, blocks) document = DOM.create_element() entity_map = content_state.get('entityMap', {}) min_depth = 0 for block in 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, entity_map, wrapper_state): if block['inlineStyleRanges'] or block['entityRanges']: content = DOM.create_element() entity_state = EntityState(self.entity_decorators, entity_map) style_state = StyleState(self.style_map) for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) style_state.apply(command) # Decorators are not rendered inside entities. if entity_state.has_no_entity() and self.has_decorators: decorated_node = render_decorators(self.composite_decorators, text, block, wrapper_state.blocks) else: decorated_node = text styled_node = style_state.render_styles(decorated_node, block, wrapper_state.blocks) entity_node = entity_state.render_entities(styled_node) 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 self.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 update_stack(self, options, depth): if depth >= self.stack.length(): # If the depth is gte the stack length, we need more wrappers. depth_levels = range(self.stack.length(), depth + 1) for level in depth_levels: new_wrapper = Wrapper(level, options) # Determine where to append the new wrapper. if self.stack.head().last_child is None: # If there is no content in the current wrapper, we need # to add an intermediary node. props = dict(options.props) props['block'] = { 'type': options.type, 'depth': depth, 'data': {}, } props['blocks'] = self.blocks wrapper_parent = DOM.create_element(options.element, props) DOM.append_child(self.stack.head().elt, wrapper_parent) else: # Otherwise we can append at the end of the last child. wrapper_parent = self.stack.head().last_child DOM.append_child(wrapper_parent, new_wrapper.elt) self.stack.append(new_wrapper) else: # Cut the stack to where it now stops, and add new wrapper. self.stack.slice(depth) self.stack.append(Wrapper(depth, options))
def render_block(self, block, entity_map, wrapper_state): content = DOM.create_element() entity_state = EntityState(self.entity_decorators, entity_map) style_state = StyleState(self.style_map) for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) style_state.apply(command) # Decorators are not rendered inside entities. if text and entity_state.has_no_entity() and len( self.composite_decorators) > 0: decorated_node = render_decorators(self.composite_decorators, text, block) else: decorated_node = text styled_node = style_state.render_styles(decorated_node) entity_node = entity_state.render_entities(styled_node) if entity_node is not None: DOM.append_child(content, entity_node) if styled_node != entity_node: DOM.append_child(content, styled_node) return wrapper_state.element_for(block, content)
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 update_stack(self, options, depth): if depth >= self.stack.length(): # If the depth is gte the stack length, we need more wrappers. depth_levels = range(self.stack.length(), depth + 1) for level in depth_levels: new_wrapper = Wrapper(level, options.wrapper) wrapper_children = DOM.get_children(self.stack.head().elt) # Determine where to append the new wrapper. if len(wrapper_children) == 0: # If there is no content in the current wrapper, we need # to add an intermediary node. wrapper_parent = DOM.create_element( options.element[0], options.element[1]) DOM.append_child(self.stack.head().elt, wrapper_parent) else: # Otherwise we can append at the end of the last child. wrapper_parent = wrapper_children[-1] DOM.append_child(wrapper_parent, new_wrapper.elt) self.stack.append(new_wrapper) else: # Cut the stack to where it now stops, and add new wrapper. self.stack.slice(depth) self.stack.append(Wrapper(depth, options.wrapper))
def create_nodes(self, text, block=None, entity_stack=None): if entity_stack: text_children = [DOM.create_text_node(text)] else: text_children = get_decorations(self.composite_decorators, text, block) if self.is_unstyled(): return list(text_children) else: tags = self.get_style_tags() node = DOM.create_element(tags[0]) child = node # Nest the tags. # Set the text and style attribute (if any) on the deepest node. for tag in tags[1:]: new_child = DOM.create_element(tag) DOM.append_child(child, new_child) child = new_child style_value = self.get_style_value() if style_value: DOM.set_attribute(child, 'style', style_value) for text_child in text_children: DOM.append_child(child, text_child) return [node]
def __init__(self, root_element, entity_decorators, entity_map): self.entity_decorators = entity_decorators self.entity_map = entity_map stack_start = DOM.create_document_fragment() DOM.append_child(root_element, stack_start) self.entity_stack = [(stack_start, {})]
def start_command(self, command): entity_details = self.get_entity_details(command) decorator = self.get_entity_decorator(entity_details) new_element = decorator.render(entity_details) DOM.append_child(self.current_parent(), new_element) self.entity_stack.append([new_element, entity_details])
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 clean_up(self): """ Special method to handle a rare corner case: if there is no block at depth 0, we need to add the wrapper that contains the whole tree to the document. """ document_length = len(DOM.get_children(self.document)) if document_length == 0 and self.stack.length() != 0: DOM.append_child(self.document, self.stack.tail().elt)
def parent_for(self, options, depth, elt): if options.wrapper: parent = self.get_wrapper_elt(options, depth) DOM.append_child(parent, elt) self.stack.stack[-1].last_child = elt else: # Reset the stack if there is no wrapper. self.stack = WrapperStack() parent = elt return parent
def render_decorators(decorators: CompositeDecorators, text: str, block: Block, blocks: Sequence[Block]) -> Element: decorated_children = list(apply_decorators(decorators, text, block, blocks)) if len(decorated_children) == 1: decorated_node = decorated_children[0] else: decorated_node = DOM.create_element() for decorated_child in decorated_children: DOM.append_child(decorated_node, decorated_child) return decorated_node
def render_decorators(decorators, text, block, blocks): decorated_children = list(apply_decorators(decorators, text, block, blocks)) if len(decorated_children) == 1: decorated_node = decorated_children[0] else: decorated_node = DOM.create_element() for decorated_child in decorated_children: DOM.append_child(decorated_node, decorated_child) return decorated_node
def render_decorators(decorators, text, block): decorated_children = list(apply_decorators(decorators, text, block)) if len(decorated_children) == 1: decorated_node = decorated_children[0] else: decorated_node = DOM.create_element() for decorated_child in decorated_children: DOM.append_child(decorated_node, decorated_child) return decorated_node
def parent_for(self, options: Options, depth: int, elt: Element) -> Element: if options.wrapper: parent = self.get_wrapper_elt(options, depth) DOM.append_child(parent, elt) self.stack.stack[-1].last_child = elt else: # Reset the stack if there is no wrapper. if self.stack.length() > 0: self.stack = WrapperStack() parent = elt return parent
def set_wrapper(self, options=[], depth=0): if len(options) == 0: element = DOM.create_document_fragment() else: element = DOM.create_element(options[0], options[1]) new_wrapper = [element, depth, options] if depth >= len(self.wrapper_stack): DOM.append_child(DOM.get_children(self.get_wrapper_elt())[-1], element) self.wrapper_stack.append(new_wrapper) else: # Cut the stack to where it now stops, and add new wrapper. self.wrapper_stack = self.wrapper_stack[:depth] + [new_wrapper]
def render_entities(self, style_node: Element, block: Block, blocks: Sequence[Block]) -> Element: # We have a complete (start, stop) entity to render. if self.completed_entity is not None: entity_details = self.get_entity_details(self.completed_entity) options = Options.get( self.entity_options, entity_details["type"], ENTITY_TYPES.FALLBACK, ) props = entity_details["data"].copy() props["entity"] = { "type": entity_details["type"], "mutability": entity_details["mutability"] if "mutability" in entity_details else None, "block": block, "blocks": blocks, "entity_range": { "key": self.completed_entity }, } if len(self.element_stack) == 1: children = self.element_stack[0] else: children = DOM.create_element() for n in self.element_stack: DOM.append_child(children, n) self.completed_entity = None self.element_stack = [] # Is there still another entity? (adjacent) if so add the current style_node for it. if self.has_entity(): self.element_stack.append(style_node) return DOM.create_element(options.element, props, children) if self.has_entity(): self.element_stack.append(style_node) return None return style_node
def render_embed(self, props): embed_pre_process = app.config.get("EMBED_PRE_PROCESS") if embed_pre_process: for callback in embed_pre_process: callback(props["data"]) # we use superdesk.etree.parse_html instead of DOM.parse_html as the later modify the content # and we use directly the wrapping <div> returned with "content='html'". This works because # we use the lxml engine with DraftJSExporter. div = parse_html(props["data"]["html"], content="html") div.set("class", "embed-block") description = props.get("description") if description: p = DOM.create_element("p", {"class": "embed-block__description"}, description) DOM.append_child(div, p) return div
def element_for(self, block): type_ = block.get('type', 'unstyled') depth = block.get('depth', 0) options = Options.for_block(self.block_map, type_) # Make an element from the options specified in the block map. elt = DOM.create_element(options.element[0], options.element[1]) parent = self.parent_for(options, depth) DOM.append_child(parent, elt) # At level 0, the element is added to the document. if depth == 0: DOM.append_child(self.document, parent) return elt
def render_embed(self, props): embed_pre_process = app.config.get('EMBED_PRE_PROCESS') if embed_pre_process: for callback in embed_pre_process: callback(props['data']) # we use superdesk.etree.parse_html instead of DOM.parse_html as the later modify the content # and we use directly the wrapping <div> returned with "content='html'". This works because # we use the lxml engine with DraftJSExporter. div = parse_html(props['data']['html'], content='html') div.set('class', 'embed-block') description = props.get('description') if description: p = DOM.create_element('p', {'class': 'embed-block__description'}, description) DOM.append_child(div, p) return div
def element_for(self, block): type = block.get('type', 'unstyled') depth = block.get('depth', 0) block_options = self.get_block_options(type) # Make an element from the options specified in the block map. elt_options = self.map_element_options(block_options.get('element')) elt = DOM.create_element(elt_options[0], elt_options[1]) parent = self.parent_for(type, depth) DOM.append_child(parent, elt) # At level 0, the element is added to the document. if (depth == 0): DOM.append_child(self.document, parent) return elt
def render_block(self, block, entity_map, wrapper_state): if 'inlineStyleRanges' in block and block[ 'inlineStyleRanges'] or 'entityRanges' in block and block[ 'entityRanges']: content = DOM.create_element() entity_state = EntityState(self.entity_options, entity_map) style_state = StyleState(self.style_options) for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) style_state.apply(command) # Decorators are not rendered inside entities. if entity_state.has_no_entity() and self.has_decorators: decorated_node = render_decorators( self.composite_decorators, text, block, wrapper_state.blocks) else: decorated_node = text styled_node = style_state.render_styles( decorated_node, block, wrapper_state.blocks) entity_node = entity_state.render_entities(styled_node) 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 self.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 render_entities(self, style_node: Element, block: Block, blocks: Sequence[Block]) -> Element: # We have a complete (start, stop) entity to render. if self.completed_entity is not None: entity_details = self.get_entity_details(self.completed_entity) options = Options.get(self.entity_options, entity_details['type'], ENTITY_TYPES.FALLBACK) props = entity_details['data'].copy() props['entity'] = { 'type': entity_details['type'], 'mutability': entity_details['mutability'] if 'mutability' in entity_details else None, 'block': block, 'blocks': blocks, 'entity_range': { 'key': self.completed_entity, }, } if len(self.element_stack) == 1: children = self.element_stack[0] else: children = DOM.create_element() for n in self.element_stack: DOM.append_child(children, n) self.completed_entity = None self.element_stack = [] # Is there still another entity? (adjacent) if so add the current style_node for it. if self.has_entity(): self.element_stack.append(style_node) return DOM.create_element(options.element, props, children) if self.has_entity(): self.element_stack.append(style_node) return None return style_node
def create_node(self, text): text_lines = self.replace_linebreaks(text) if self.is_unstyled(): node = text_lines else: tags = self.get_style_tags() node = DOM.create_element(tags[0]) child = node # Nest the tags. # Set the text and style attribute (if any) on the deepest node. for tag in tags[1:]: new_child = DOM.create_element(tag) DOM.append_child(child, new_child) child = new_child style_value = self.get_style_value() if style_value: DOM.set_attribute(child, 'style', style_value) DOM.append_child(child, text_lines) return node
def replace_linebreaks(self, text): lines = text.split('\n') if len(lines) > 1: wrapper = DOM.create_document_fragment() DOM.append_child(wrapper, DOM.create_text_node(lines[0])) for l in lines[1:]: DOM.append_child(wrapper, DOM.create_element('br')) DOM.append_child(wrapper, DOM.create_text_node(l)) else: wrapper = DOM.create_text_node(text) return wrapper
def render_block(self, block, entity_map, wrapper_state): content = DOM.create_element() if block['inlineStyleRanges'] or block['entityRanges']: entity_state = EntityState(self.entity_decorators, entity_map) style_state = StyleState(self.style_map) for (text, commands) in self.build_command_groups(block): for command in commands: entity_state.apply(command) style_state.apply(command) # Decorators are not rendered inside entities. if text and entity_state.has_no_entity() and len( self.composite_decorators) > 0: decorated_node = render_decorators( self.composite_decorators, text, block, wrapper_state.blocks) else: decorated_node = text styled_node = style_state.render_styles( decorated_node, block, wrapper_state.blocks) entity_node = entity_state.render_entities(styled_node) if entity_node is not None: DOM.append_child(content, entity_node) if styled_node != entity_node: DOM.append_child(content, styled_node) # Fast track for blocks which do not contain styles nor entities, which is very common. else: if len(self.composite_decorators) > 0: decorated_node = render_decorators(self.composite_decorators, block['text'], block, wrapper_state.blocks) else: decorated_node = block['text'] DOM.append_child(content, decorated_node) return wrapper_state.element_for(block, content)
def render_entitities(self, root_element, style_node): stack_start = DOM.create_document_fragment() DOM.append_child(root_element, stack_start) element_stack = [stack_start] new_element = stack_start if len(self.entity_stack) == 0: DOM.append_child(root_element, style_node) else: for entity_details in self.entity_stack: decorator = self.get_entity_decorator(entity_details) props = entity_details.copy() props['children'] = style_node new_element = decorator.render(props) DOM.append_child(element_stack[-1], new_element) element_stack.append(new_element) return new_element
def render_entitities(self, root_element, style_nodes): element_stack = [] if len(self.entity_stack) == 0: for node in style_nodes: DOM.append_child(root_element, node) return root_element else: for entity_details in self.entity_stack: decorator = self.get_entity_decorator(entity_details) entity_details['children'] = style_nodes new_element = decorator.render(entity_details) if not element_stack: DOM.append_child(root_element, new_element) element_stack = [new_element] else: DOM.append_child(element_stack[-1], new_element) element_stack.append(new_element) return new_element
def test_append_child(self): parent = DOM.create_element('p') DOM.append_child(parent, DOM.create_element('span', {}, 'Test text')) self.assertEqual(DOM.render_debug(parent), '<p><span>Test text</span></p>')