| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- import re
- from typing import (
- TYPE_CHECKING,
- Any,
- Dict,
- List,
- Match,
- Optional,
- Tuple,
- Union,
- )
- from ..helpers import PREVENT_BACKSLASH
- if TYPE_CHECKING:
- from ..block_parser import BlockParser
- from ..core import BaseRenderer, BlockState
- from ..markdown import Markdown
- # https://michelf.ca/projects/php-markdown/extra/#table
- __all__ = ["table", "table_in_quote", "table_in_list"]
- TABLE_PATTERN = (
- r"^ {0,3}\|(?P<table_head>.+)\|[ \t]*\n"
- r" {0,3}\|(?P<table_align> *[-:]+[-| :]*)\|[ \t]*\n"
- r"(?P<table_body>(?: {0,3}\|.*\|[ \t]*(?:\n|$))*)\n*"
- )
- NP_TABLE_PATTERN = (
- r"^ {0,3}(?P<nptable_head>\S.*\|.*)\n"
- r" {0,3}(?P<nptable_align>[-:]+ *\|[-| :]*)\n"
- r"(?P<nptable_body>(?:.*\|.*(?:\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: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
- pos = m.end()
- header = m.group("table_head")
- align = m.group("table_align")
- thead, aligns = _process_thead(header, align)
- if not thead:
- return None
- assert aligns is not None
- rows = []
- body = m.group("table_body")
- for text in body.splitlines():
- m2 = TABLE_CELL.match(text)
- if not m2: # pragma: no cover
- return None
- row = _process_row(m2.group(1), aligns)
- if not row:
- return None
- rows.append(row)
- children = [thead, {"type": "table_body", "children": rows}]
- state.append_token({"type": "table", "children": children})
- return pos
- def parse_nptable(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]:
- header = m.group("nptable_head")
- align = m.group("nptable_align")
- thead, aligns = _process_thead(header, align)
- if not thead:
- return None
- assert aligns is not None
- rows = []
- body = m.group("nptable_body")
- for text in body.splitlines():
- row = _process_row(text, aligns)
- if not row:
- return None
- rows.append(row)
- children = [thead, {"type": "table_body", "children": rows}]
- state.append_token({"type": "table", "children": children})
- return m.end()
- def _process_thead(header: str, align: str) -> Union[Tuple[None, None], Tuple[Dict[str, Any], List[str]]]:
- 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: str, aligns: List[str]) -> Optional[Dict[str, Any]]:
- 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: "BaseRenderer", text: str) -> str:
- return "<table>\n" + text + "</table>\n"
- def render_table_head(renderer: "BaseRenderer", text: str) -> str:
- return "<thead>\n<tr>\n" + text + "</tr>\n</thead>\n"
- def render_table_body(renderer: "BaseRenderer", text: str) -> str:
- return "<tbody>\n" + text + "</tbody>\n"
- def render_table_row(renderer: "BaseRenderer", text: str) -> str:
- return "<tr>\n" + text + "</tr>\n"
- def render_table_cell(renderer: "BaseRenderer", text: str, align: Optional[str] = None, head: bool = False) -> str:
- if head:
- tag = "th"
- else:
- tag = "td"
- html = " <" + tag
- if align:
- html += ' style="text-align:' + align + '"'
- return html + ">" + text + "</" + tag + ">\n"
- def table(md: "Markdown") -> None:
- """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: "Markdown") -> None:
- """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: "Markdown") -> None:
- """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")
|