def test_render_link_marker_success(): for text in sample_texts: node = Span(_type='span', marks=['linkId'], text=text) block = Block( _type='block', children=[node.__dict__], markDefs=[{'_type': 'link', '_key': 'linkId', 'href': text}] ) assert LinkMarkerDefinition.render_text(node, 'linkId', block) == f'{text}' assert LinkMarkerDefinition.render(node, 'linkId', block) == f'<a href="{text}">{text}</a>'
def test_render_underline_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) assert UnderlineMarkerDefinition.render_text(node, 'u', block) == f'{text}' assert ( UnderlineMarkerDefinition.render(node, 'u', block) == f'<span style="text-decoration:underline;">{text}</span>' )
def _render_node(self, node: dict, context: Optional[Block] = None, list_item: bool = False) -> str: """ Call the correct render method depending on the node type. :param node: Block content node - can be block, span, or list (block). :param context: Optional context. Spans are passed with a Block instance as context for mark lookups. :param list_item: Whether we are handling a list upstream (impacts block handling). """ if is_list(node): logger.debug('Rendering node as list') block = Block(**node, marker_definitions=self._custom_marker_definitions) return self._render_list(block, context) elif is_block(node): logger.debug('Rendering node as block') block = Block(**node, marker_definitions=self._custom_marker_definitions) return self._render_block(block, list_item=list_item) elif is_span(node): logger.debug('Rendering node as span') span = Span(**node) context = cast(Block, context) # context should always be a Block here return self._render_span(span, block=context) elif self._custom_serializers.get(node.get('_type', '')): return self._custom_serializers.get(node.get('_type', ''))( node, context, list_item) # type: ignore else: if '_type' in node: raise MissingSerializerError( f'Found unhandled node type: {node["_type"]}. ' 'Most likely this requires a custom serializer.') else: raise UnhandledNodeError( f'Received node that we cannot handle: {node}')
def render(self) -> str: """Render HTML from self._blocks.""" logger.debug('Rendering HTML') if not self._blocks: return '' result = '' list_nodes: List[Dict] = [] for node in self._blocks: if list_nodes and not is_list(node): tree = self._normalize_list_tree(list_nodes) result += ''.join([ self._render_node(n, Block(**node), list_item=True) for n in tree ]) list_nodes = [] # reset list_nodes if is_list(node): list_nodes.append(node) continue # handle all elements ^ when the list ends result += self._render_node( node) # render non-list nodes immediately if list_nodes: tree = self._normalize_list_tree(list_nodes) result += ''.join( self._render_node(n, Block(**node), list_item=True) for n in tree) result = result.strip() if self._wrapper_element: return f'<{self._wrapper_element}>{result}</{self._wrapper_element}>' return result
def _render_span(self, span: Span, block: Block) -> str: logger.debug('Rendering span') result: str = '' prev_node, next_node = block.get_node_siblings(span) prev_marks = prev_node.get('marks', []) if prev_node else [] next_marks = next_node.get('marks', []) if next_node else [] sorted_marks = sorted(span.marks, key=lambda x: -block.marker_frequencies[x]) for mark in sorted_marks: if mark in prev_marks: continue marker_callable = block.marker_definitions.get( mark, DefaultMarkerDefinition)() result += marker_callable.render_prefix(span, mark, block) # to avoid rendering the text multiple times, # only the first custom mark will be used custom_mark_text_rendered = False if sorted_marks: for mark in sorted_marks: if custom_mark_text_rendered or mark in prev_marks: continue marker_callable = block.marker_definitions.get( mark, DefaultMarkerDefinition)() result += marker_callable.render_text(span, mark, block) custom_mark_text_rendered = True if not custom_mark_text_rendered: result += html.escape(span.text).replace('\n', '<br/>') for mark in reversed(sorted_marks): if mark in next_marks: continue marker_callable = block.marker_definitions.get( mark, DefaultMarkerDefinition)() result += marker_callable.render_suffix(span, mark, block) return result
def test_render_comment_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) assert CommentMarkerDefinition.render(node, 'comment', block) == f'<!-- {text} -->'
def test_render_strikethrough_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) assert StrikeThroughMarkerDefinition.render_text(node, 'strike', block) == f'{text}' assert StrikeThroughMarkerDefinition.render(node, 'strike', block) == f'<del>{text}</del>'
def test_render_strong_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) assert StrongMarkerDefinition.render_text(node, 'strong', block) == f'{text}' assert StrongMarkerDefinition.render(node, 'strong', block) == f'<strong>{text}</strong>'
def test_render_emphasis_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) assert EmphasisMarkerDefinition.render_text(node, 'em', block) == f'{text}' assert EmphasisMarkerDefinition.render(node, 'em', block) == f'<em>{text}</em>'