|
1 | 1 | """Renderer classes for md2zhihu""" |
2 | 2 |
|
3 | | -import pprint |
4 | | -from typing import List |
5 | | -from typing import Optional |
6 | | - |
7 | | -from ..utils import add_paragraph_end |
8 | | -from ..utils import indent |
9 | | -from ..utils import msg |
10 | | -from ..utils import strip_paragraph_end |
11 | | - |
12 | | - |
13 | | -class MDRender(object): |
14 | | - # platform specific renderer |
15 | | - |
16 | | - def __init__(self, conf, features: dict): |
17 | | - self.conf = conf |
18 | | - self.features = features |
19 | | - |
20 | | - def render_node(self, rnode): |
21 | | - """ |
22 | | - Render a AST node into lines of text |
23 | | -
|
24 | | - Args: |
25 | | - rnode is a RenderNode instance |
26 | | - """ |
27 | | - |
28 | | - n = rnode.node |
29 | | - typ = n["type"] |
30 | | - |
31 | | - # customized renderers: |
32 | | - |
33 | | - lines = render_with_features(self, rnode, self.features) |
34 | | - if lines is not None: |
35 | | - return lines |
36 | | - else: |
37 | | - # can not render, continue with default handler |
38 | | - pass |
39 | | - |
40 | | - # default renderers: |
41 | | - |
42 | | - if typ == "thematic_break": |
43 | | - return ["---", ""] |
44 | | - |
45 | | - if typ == "paragraph": |
46 | | - lines = self.render(rnode) |
47 | | - return "".join(lines).split("\n") + [""] |
48 | | - |
49 | | - if typ == "text": |
50 | | - return [n["text"]] |
51 | | - |
52 | | - if typ == "strong": |
53 | | - lines = self.render(rnode) |
54 | | - lines[0] = "**" + lines[0] |
55 | | - lines[-1] = lines[-1] + "**" |
56 | | - return lines |
57 | | - |
58 | | - if typ == "math_block": |
59 | | - return ["$$", n["text"], "$$"] |
60 | | - |
61 | | - if typ == "math_inline": |
62 | | - return ["$$ " + n["text"].strip() + " $$"] |
63 | | - |
64 | | - if typ == "table": |
65 | | - return self.render(rnode) + [""] |
66 | | - |
67 | | - if typ == "table_head": |
68 | | - alignmap = { |
69 | | - "left": ":--", |
70 | | - "right": "--:", |
71 | | - "center": ":-:", |
72 | | - None: "---", |
73 | | - } |
74 | | - lines = self.render(rnode) |
75 | | - aligns = [alignmap[x["align"]] for x in n["children"]] |
76 | | - aligns = "| " + " | ".join(aligns) + " |" |
77 | | - return ["| " + " | ".join(lines) + " |", aligns] |
78 | | - |
79 | | - if typ == "table_cell": |
80 | | - lines = self.render(rnode) |
81 | | - return ["".join(lines)] |
82 | | - |
83 | | - if typ == "table_body": |
84 | | - return self.render(rnode) |
85 | | - |
86 | | - if typ == "table_row": |
87 | | - lines = self.render(rnode) |
88 | | - return ["| " + " | ".join(lines) + " |"] |
89 | | - |
90 | | - if typ == "block_code": |
91 | | - # remove the last \n |
92 | | - return ["```" + (n["info"] or "")] + n["text"][:-1].split("\n") + ["```", ""] |
93 | | - |
94 | | - if typ == "codespan": |
95 | | - return [("`" + n["text"] + "`")] |
96 | | - |
97 | | - if typ == "image": |
98 | | - if n["title"] is None: |
99 | | - return ["".format(**n)] |
100 | | - else: |
101 | | - return ["".format(**n)] |
102 | | - |
103 | | - if typ == "list": |
104 | | - lines = self.render(rnode) |
105 | | - return add_paragraph_end(lines) |
106 | | - |
107 | | - if typ == "list_item": |
108 | | - lines = self.render(rnode) |
109 | | - |
110 | | - # parent is a `list` node |
111 | | - parent = rnode.parent |
112 | | - assert parent.node["type"] == "list" |
113 | | - |
114 | | - head = "- " |
115 | | - if parent.node["ordered"]: |
116 | | - head = "1. " |
117 | | - |
118 | | - lines[0] = head + lines[0] |
119 | | - lines = lines[0:1] + [indent(x) for x in lines[1:]] |
120 | | - return lines |
121 | | - |
122 | | - if typ == "block_text": |
123 | | - lines = self.render(rnode) |
124 | | - return "".join(lines).split("\n") |
125 | | - |
126 | | - if typ == "block_quote": |
127 | | - lines = self.render(rnode) |
128 | | - lines = strip_paragraph_end(lines) |
129 | | - lines = ["> " + x for x in lines] |
130 | | - return lines + [""] |
131 | | - |
132 | | - if typ == "newline": |
133 | | - return [""] |
134 | | - |
135 | | - if typ == "block_html": |
136 | | - return add_paragraph_end([n["text"]]) |
137 | | - |
138 | | - if typ == "link": |
139 | | - # TODO title |
140 | | - lines = self.render(rnode) |
141 | | - lines[0] = "[" + lines[0] |
142 | | - lines[-1] = lines[-1] + "](" + n["link"] + ")" |
143 | | - |
144 | | - return lines |
145 | | - |
146 | | - if typ == "heading": |
147 | | - lines = self.render(rnode) |
148 | | - lines[0] = "#" * n["level"] + " " + lines[0] |
149 | | - return lines + [""] |
150 | | - |
151 | | - if typ == "strikethrough": |
152 | | - lines = self.render(rnode) |
153 | | - lines[0] = "~~" + lines[0] |
154 | | - lines[-1] = lines[-1] + "~~" |
155 | | - return lines |
156 | | - |
157 | | - if typ == "emphasis": |
158 | | - lines = self.render(rnode) |
159 | | - lines[0] = "*" + lines[0] |
160 | | - lines[-1] = lines[-1] + "*" |
161 | | - return lines |
162 | | - |
163 | | - if typ == "inline_html": |
164 | | - return [n["text"]] |
165 | | - |
166 | | - if typ == "linebreak": |
167 | | - return [" \n"] |
168 | | - |
169 | | - print(typ, n.keys()) |
170 | | - pprint.pprint(n) |
171 | | - return ["***:" + typ] |
172 | | - |
173 | | - def render(self, rnode) -> List[str]: |
174 | | - rst = [] |
175 | | - for n in rnode.node["children"]: |
176 | | - child = rnode.new_child(n) |
177 | | - lines = self.render_node(child) |
178 | | - rst.extend(lines) |
179 | | - |
180 | | - return rst |
181 | | - |
182 | | - def msg(self, *args): |
183 | | - msg(*args) |
184 | | - |
185 | | - |
186 | | -class RenderNode(object): |
187 | | - """ |
188 | | - RenderNode is a container of current ast-node and parent |
189 | | - """ |
190 | | - |
191 | | - def __init__(self, n, parent=None): |
192 | | - """ |
193 | | - :param n: ast node: a normal dictionary such as {'type': 'text' ... } |
194 | | - :param parent: parent RenderNode |
195 | | - """ |
196 | | - self.node = n |
197 | | - |
198 | | - self.level = 0 |
199 | | - |
200 | | - # parent RenderNode |
201 | | - self.parent = parent |
202 | | - |
203 | | - def new_child(self, n): |
204 | | - c = RenderNode(n, parent=self) |
205 | | - c.level = self.level + 1 |
206 | | - return c |
207 | | - |
208 | | - def to_str(self): |
209 | | - t = "{}".format(self.node.get("type")) |
210 | | - if self.parent is None: |
211 | | - return t |
212 | | - |
213 | | - return self.parent.to_str() + " -> " + t |
214 | | - |
215 | | - |
216 | | -# features: {typ:action(), typ2:{subtyp:action()}} |
217 | | -def render_with_features(mdrender, rnode: RenderNode, features=None) -> Optional[List[str]]: |
218 | | - if features is None: |
219 | | - features = {} |
220 | | - |
221 | | - n = rnode.node |
222 | | - |
223 | | - node_type = n["type"] |
224 | | - |
225 | | - if node_type not in features: |
226 | | - if "*" in features: |
227 | | - return features["*"](mdrender, rnode) |
228 | | - else: |
229 | | - return None |
230 | | - |
231 | | - type_handler = features[node_type] |
232 | | - if callable(type_handler): |
233 | | - return type_handler(mdrender, rnode) |
234 | | - |
235 | | - # subtype is info, the type after "```" |
236 | | - lang = n["info"] or "" |
237 | | - |
238 | | - if lang in type_handler: |
239 | | - return type_handler[lang](mdrender, rnode) |
240 | | - |
241 | | - if "*" in type_handler: |
242 | | - return type_handler["*"](mdrender, rnode) |
243 | | - |
244 | | - return None |
| 3 | +from .md_render import MDRender |
| 4 | +from .render_node import RenderNode |
0 commit comments