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