markdown.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
  2. from .block_parser import BlockParser
  3. from .core import BaseRenderer, BlockState
  4. from .inline_parser import InlineParser
  5. from .plugins import Plugin
  6. class Markdown:
  7. """Markdown instance to convert markdown text into HTML or other formats.
  8. Here is an example with the HTMLRenderer::
  9. from mistune import HTMLRenderer
  10. md = Markdown(renderer=HTMLRenderer(escape=False))
  11. md('hello **world**')
  12. :param renderer: a renderer to convert parsed tokens
  13. :param block: block level syntax parser
  14. :param inline: inline level syntax parser
  15. :param plugins: mistune plugins to use
  16. """
  17. def __init__(
  18. self,
  19. renderer: Optional[BaseRenderer] = None,
  20. block: Optional[BlockParser] = None,
  21. inline: Optional[InlineParser] = None,
  22. plugins: Optional[Iterable[Plugin]] = None,
  23. ):
  24. if block is None:
  25. block = BlockParser()
  26. if inline is None:
  27. inline = InlineParser()
  28. self.renderer = renderer
  29. self.block: BlockParser = block
  30. self.inline: InlineParser = inline
  31. self.before_parse_hooks: List[Callable[["Markdown", BlockState], None]] = []
  32. self.before_render_hooks: List[Callable[["Markdown", BlockState], Any]] = []
  33. self.after_render_hooks: List[
  34. Callable[["Markdown", Union[str, List[Dict[str, Any]]], BlockState], Union[str, List[Dict[str, Any]]]]
  35. ] = []
  36. if plugins:
  37. for plugin in plugins:
  38. plugin(self)
  39. def use(self, plugin: Plugin) -> None:
  40. plugin(self)
  41. def render_state(self, state: BlockState) -> Union[str, List[Dict[str, Any]]]:
  42. data = self._iter_render(state.tokens, state)
  43. if self.renderer:
  44. return self.renderer(data, state)
  45. return list(data)
  46. def _iter_render(self, tokens: Iterable[Dict[str, Any]], state: BlockState) -> Iterable[Dict[str, Any]]:
  47. for tok in tokens:
  48. if "children" in tok:
  49. children = self._iter_render(tok["children"], state)
  50. tok["children"] = list(children)
  51. elif "text" in tok:
  52. text = tok.pop("text")
  53. # process inline text
  54. # avoid striping emsp or other unicode spaces
  55. tok["children"] = self.inline(text.strip(" \r\n\t\f"), state.env)
  56. yield tok
  57. def parse(self, s: str, state: Optional[BlockState] = None) -> Tuple[Union[str, List[Dict[str, Any]]], BlockState]:
  58. """Parse and convert the given markdown string. If renderer is None,
  59. the returned **result** will be parsed markdown tokens.
  60. :param s: markdown string
  61. :param state: instance of BlockState
  62. :returns: result, state
  63. """
  64. if state is None:
  65. state = self.block.state_cls()
  66. # normalize line separator
  67. s = s.replace("\r\n", "\n")
  68. s = s.replace("\r", "\n")
  69. if not s.endswith("\n"):
  70. s += "\n"
  71. state.process(s)
  72. for hook in self.before_parse_hooks:
  73. hook(self, state)
  74. self.block.parse(state)
  75. for hook2 in self.before_render_hooks:
  76. hook2(self, state)
  77. result = self.render_state(state)
  78. for hook3 in self.after_render_hooks:
  79. result = hook3(self, result, state)
  80. return result, state
  81. def read(
  82. self, filepath: str, encoding: str = "utf-8", state: Optional[BlockState] = None
  83. ) -> Tuple[Union[str, List[Dict[str, Any]]], BlockState]:
  84. if state is None:
  85. state = self.block.state_cls()
  86. state.env["__file__"] = filepath
  87. with open(filepath, "rb") as f:
  88. s = f.read()
  89. s2 = s.decode(encoding)
  90. return self.parse(s2, state)
  91. def __call__(self, s: str) -> Union[str, List[Dict[str, Any]]]:
  92. if s is None:
  93. s = "\n"
  94. return self.parse(s)[0]