import re from ..helpers import PREVENT_BACKSLASH # https://michelf.ca/projects/php-markdown/extra/#table __all__ = ['table', 'table_in_quote', 'table_in_list'] TABLE_PATTERN = ( r'^ {0,3}\|(?P.+)\|[ \t]*\n' r' {0,3}\|(?P *[-:]+[-| :]*)\|[ \t]*\n' r'(?P(?: {0,3}\|.*\|[ \t]*(?:\n|$))*)\n*' ) NP_TABLE_PATTERN = ( r'^ {0,3}(?P\S.*\|.*)\n' r' {0,3}(?P[-:]+ *\|[-| :]*)\n' r'(?P(?:.*\|.*(?:\n|$))*)\n*' ) TABLE_CELL = re.compile(r'^ {0,3}\|(.+)\|[ \t]*$') CELL_SPLIT = re.compile(r' *' + PREVENT_BACKSLASH + r'\| *') ALIGN_CENTER = re.compile(r'^ *:-+: *$') ALIGN_LEFT = re.compile(r'^ *:-+ *$') ALIGN_RIGHT = re.compile(r'^ *-+: *$') def parse_table(block, m, state): pos = m.end() header = m.group('table_head') align = m.group('table_align') thead, aligns = _process_thead(header, align) if not thead: return rows = [] body = m.group('table_body') for text in body.splitlines(): m = TABLE_CELL.match(text) if not m: # pragma: no cover return row = _process_row(m.group(1), aligns) if not row: return rows.append(row) children = [thead, {'type': 'table_body', 'children': rows}] state.append_token({'type': 'table', 'children': children}) return pos def parse_nptable(block, m, state): header = m.group('nptable_head') align = m.group('nptable_align') thead, aligns = _process_thead(header, align) if not thead: return rows = [] body = m.group('nptable_body') for text in body.splitlines(): row = _process_row(text, aligns) if not row: return rows.append(row) children = [thead, {'type': 'table_body', 'children': rows}] state.append_token({'type': 'table', 'children': children}) return m.end() def _process_thead(header, align): headers = CELL_SPLIT.split(header) aligns = CELL_SPLIT.split(align) if len(headers) != len(aligns): return None, None for i, v in enumerate(aligns): if ALIGN_CENTER.match(v): aligns[i] = 'center' elif ALIGN_LEFT.match(v): aligns[i] = 'left' elif ALIGN_RIGHT.match(v): aligns[i] = 'right' else: aligns[i] = None children = [ { 'type': 'table_cell', 'text': text.strip(), 'attrs': {'align': aligns[i], 'head': True} } for i, text in enumerate(headers) ] thead = {'type': 'table_head', 'children': children} return thead, aligns def _process_row(text, aligns): cells = CELL_SPLIT.split(text) if len(cells) != len(aligns): return None children = [ { 'type': 'table_cell', 'text': text.strip(), 'attrs': {'align': aligns[i], 'head': False} } for i, text in enumerate(cells) ] return {'type': 'table_row', 'children': children} def render_table(renderer, text): return '\n' + text + '
\n' def render_table_head(renderer, text): return '\n\n' + text + '\n\n' def render_table_body(renderer, text): return '\n' + text + '\n' def render_table_row(renderer, text): return '\n' + text + '\n' def render_table_cell(renderer, text, align=None, head=False): if head: tag = 'th' else: tag = 'td' html = ' <' + tag if align: html += ' style="text-align:' + align + '"' return html + '>' + text + '\n' def table(md): """A mistune plugin to support table, spec defined at https://michelf.ca/projects/php-markdown/extra/#table Here is an example: .. code-block:: text First Header | Second Header ------------- | ------------- Content Cell | Content Cell Content Cell | Content Cell :param md: Markdown instance """ md.block.register('table', TABLE_PATTERN, parse_table, before='paragraph') md.block.register('nptable', NP_TABLE_PATTERN, parse_nptable, before='paragraph') if md.renderer and md.renderer.NAME == 'html': md.renderer.register('table', render_table) md.renderer.register('table_head', render_table_head) md.renderer.register('table_body', render_table_body) md.renderer.register('table_row', render_table_row) md.renderer.register('table_cell', render_table_cell) def table_in_quote(md): """Enable table plugin in block quotes.""" md.block.insert_rule(md.block.block_quote_rules, 'table', before='paragraph') md.block.insert_rule(md.block.block_quote_rules, 'nptable', before='paragraph') def table_in_list(md): """Enable table plugin in list.""" md.block.insert_rule(md.block.list_rules, 'table', before='paragraph') md.block.insert_rule(md.block.list_rules, 'nptable', before='paragraph')