def_list.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import re
  2. from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Match
  3. from ..util import strip_end
  4. if TYPE_CHECKING:
  5. from ..block_parser import BlockParser
  6. from ..core import BaseRenderer, BlockState
  7. from ..markdown import Markdown
  8. __all__ = ["def_list"]
  9. # https://michelf.ca/projects/php-markdown/extra/#def-list
  10. DEF_PATTERN = (
  11. r"^(?P<def_list_head>(?:[^\n]+\n)+?)"
  12. r"\n?(?:"
  13. r"\:[ \t]+.*\n"
  14. r"(?:[^\n]+\n)*" # lazy continue line
  15. r"(?:(?:[ \t]*\n)*[ \t]+[^\n]+\n)*"
  16. r"(?:[ \t]*\n)*"
  17. r")+"
  18. )
  19. DEF_RE = re.compile(DEF_PATTERN, re.M)
  20. DD_START_RE = re.compile(r"^:[ \t]+", re.M)
  21. TRIM_RE = re.compile(r"^ {0,4}", re.M)
  22. HAS_BLANK_LINE_RE = re.compile(r"\n[ \t]*\n$")
  23. def parse_def_list(block: "BlockParser", m: Match[str], state: "BlockState") -> int:
  24. pos = m.end()
  25. children = list(_parse_def_item(block, m))
  26. m2 = DEF_RE.match(state.src, pos)
  27. while m2:
  28. children.extend(list(_parse_def_item(block, m2)))
  29. pos = m2.end()
  30. m2 = DEF_RE.match(state.src, pos)
  31. state.append_token(
  32. {
  33. "type": "def_list",
  34. "children": children,
  35. }
  36. )
  37. return pos
  38. def _parse_def_item(block: "BlockParser", m: Match[str]) -> Iterable[Dict[str, Any]]:
  39. head = m.group("def_list_head")
  40. for line in head.splitlines():
  41. yield {
  42. "type": "def_list_head",
  43. "text": line,
  44. }
  45. src = m.group(0)
  46. end = len(head)
  47. m2 = DD_START_RE.search(src, end)
  48. assert m2 is not None
  49. start = m2.start()
  50. prev_blank_line = src[end:start] == "\n"
  51. while m2:
  52. m2 = DD_START_RE.search(src, start + 1)
  53. if not m2:
  54. break
  55. end = m2.start()
  56. text = src[start:end].replace(":", " ", 1)
  57. children = _process_text(block, text, prev_blank_line)
  58. prev_blank_line = bool(HAS_BLANK_LINE_RE.search(text))
  59. yield {
  60. "type": "def_list_item",
  61. "children": children,
  62. }
  63. start = end
  64. text = src[start:].replace(":", " ", 1)
  65. children = _process_text(block, text, prev_blank_line)
  66. yield {
  67. "type": "def_list_item",
  68. "children": children,
  69. }
  70. def _process_text(block: "BlockParser", text: str, loose: bool) -> List[Any]:
  71. text = TRIM_RE.sub("", text)
  72. state = block.state_cls()
  73. state.process(strip_end(text))
  74. # use default list rules
  75. block.parse(state, block.list_rules)
  76. tokens = state.tokens
  77. if not loose and len(tokens) == 1 and tokens[0]["type"] == "paragraph":
  78. tokens[0]["type"] = "block_text"
  79. return tokens
  80. def render_def_list(renderer: "BaseRenderer", text: str) -> str:
  81. return "<dl>\n" + text + "</dl>\n"
  82. def render_def_list_head(renderer: "BaseRenderer", text: str) -> str:
  83. return "<dt>" + text + "</dt>\n"
  84. def render_def_list_item(renderer: "BaseRenderer", text: str) -> str:
  85. return "<dd>" + text + "</dd>\n"
  86. def def_list(md: "Markdown") -> None:
  87. """A mistune plugin to support def list, spec defined at
  88. https://michelf.ca/projects/php-markdown/extra/#def-list
  89. Here is an example:
  90. .. code-block:: text
  91. Apple
  92. : Pomaceous fruit of plants of the genus Malus in
  93. the family Rosaceae.
  94. Orange
  95. : The fruit of an evergreen tree of the genus Citrus.
  96. It will be converted into HTML:
  97. .. code-block:: html
  98. <dl>
  99. <dt>Apple</dt>
  100. <dd>Pomaceous fruit of plants of the genus Malus in
  101. the family Rosaceae.</dd>
  102. <dt>Orange</dt>
  103. <dd>The fruit of an evergreen tree of the genus Citrus.</dd>
  104. </dl>
  105. :param md: Markdown instance
  106. """
  107. md.block.register("def_list", DEF_PATTERN, parse_def_list, before="paragraph")
  108. if md.renderer and md.renderer.NAME == "html":
  109. md.renderer.register("def_list", render_def_list)
  110. md.renderer.register("def_list_head", render_def_list_head)
  111. md.renderer.register("def_list_item", render_def_list_item)