1 | #include <stdlib.h> |
2 | #include <stdio.h> |
3 | #include <string.h> |
4 | #include <assert.h> |
5 | #include "cmark_ctype.h" |
6 | #include "config.h" |
7 | #include "cmark.h" |
8 | #include "node.h" |
9 | #include "buffer.h" |
10 | #include "houdini.h" |
11 | #include "scanners.h" |
12 | |
13 | #define BUFFER_SIZE 100 |
14 | |
15 | // Functions to convert cmark_nodes to HTML strings. |
16 | |
17 | static void escape_html(cmark_strbuf *dest, const unsigned char *source, |
18 | bufsize_t length) { |
19 | houdini_escape_html0(dest, source, length, 0); |
20 | } |
21 | |
22 | static CMARK_INLINE void cr(cmark_strbuf *html) { |
23 | if (html->size && html->ptr[html->size - 1] != '\n') |
24 | cmark_strbuf_putc(html, '\n'); |
25 | } |
26 | |
27 | struct render_state { |
28 | cmark_strbuf *html; |
29 | cmark_node *plain; |
30 | }; |
31 | |
32 | static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html, |
33 | int options) { |
34 | char buffer[BUFFER_SIZE]; |
35 | if (CMARK_OPT_SOURCEPOS & options) { |
36 | snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"" , |
37 | cmark_node_get_start_line(node), cmark_node_get_start_column(node), |
38 | cmark_node_get_end_line(node), cmark_node_get_end_column(node)); |
39 | cmark_strbuf_puts(html, buffer); |
40 | } |
41 | } |
42 | |
43 | static int S_render_node(cmark_node *node, cmark_event_type ev_type, |
44 | struct render_state *state, int options) { |
45 | cmark_node *parent; |
46 | cmark_node *grandparent; |
47 | cmark_strbuf *html = state->html; |
48 | char start_heading[] = "<h0" ; |
49 | char end_heading[] = "</h0" ; |
50 | bool tight; |
51 | char buffer[BUFFER_SIZE]; |
52 | |
53 | bool entering = (ev_type == CMARK_EVENT_ENTER); |
54 | |
55 | if (state->plain == node) { // back at original node |
56 | state->plain = NULL; |
57 | } |
58 | |
59 | if (state->plain != NULL) { |
60 | switch (node->type) { |
61 | case CMARK_NODE_TEXT: |
62 | case CMARK_NODE_CODE: |
63 | case CMARK_NODE_HTML_INLINE: |
64 | escape_html(html, node->data, node->len); |
65 | break; |
66 | |
67 | case CMARK_NODE_LINEBREAK: |
68 | case CMARK_NODE_SOFTBREAK: |
69 | cmark_strbuf_putc(html, ' '); |
70 | break; |
71 | |
72 | default: |
73 | break; |
74 | } |
75 | return 1; |
76 | } |
77 | |
78 | switch (node->type) { |
79 | case CMARK_NODE_DOCUMENT: |
80 | break; |
81 | |
82 | case CMARK_NODE_BLOCK_QUOTE: |
83 | if (entering) { |
84 | cr(html); |
85 | cmark_strbuf_puts(html, "<blockquote" ); |
86 | S_render_sourcepos(node, html, options); |
87 | cmark_strbuf_puts(html, ">\n" ); |
88 | } else { |
89 | cr(html); |
90 | cmark_strbuf_puts(html, "</blockquote>\n" ); |
91 | } |
92 | break; |
93 | |
94 | case CMARK_NODE_LIST: { |
95 | cmark_list_type list_type = (cmark_list_type)node->as.list.list_type; |
96 | int start = node->as.list.start; |
97 | |
98 | if (entering) { |
99 | cr(html); |
100 | if (list_type == CMARK_BULLET_LIST) { |
101 | cmark_strbuf_puts(html, "<ul" ); |
102 | S_render_sourcepos(node, html, options); |
103 | cmark_strbuf_puts(html, ">\n" ); |
104 | } else if (start == 1) { |
105 | cmark_strbuf_puts(html, "<ol" ); |
106 | S_render_sourcepos(node, html, options); |
107 | cmark_strbuf_puts(html, ">\n" ); |
108 | } else { |
109 | snprintf(buffer, BUFFER_SIZE, "<ol start=\"%d\"" , start); |
110 | cmark_strbuf_puts(html, buffer); |
111 | S_render_sourcepos(node, html, options); |
112 | cmark_strbuf_puts(html, ">\n" ); |
113 | } |
114 | } else { |
115 | cmark_strbuf_puts(html, |
116 | list_type == CMARK_BULLET_LIST ? "</ul>\n" : "</ol>\n" ); |
117 | } |
118 | break; |
119 | } |
120 | |
121 | case CMARK_NODE_ITEM: |
122 | if (entering) { |
123 | cr(html); |
124 | cmark_strbuf_puts(html, "<li" ); |
125 | S_render_sourcepos(node, html, options); |
126 | cmark_strbuf_putc(html, '>'); |
127 | } else { |
128 | cmark_strbuf_puts(html, "</li>\n" ); |
129 | } |
130 | break; |
131 | |
132 | case CMARK_NODE_HEADING: |
133 | if (entering) { |
134 | cr(html); |
135 | start_heading[2] = (char)('0' + node->as.heading.level); |
136 | cmark_strbuf_puts(html, start_heading); |
137 | S_render_sourcepos(node, html, options); |
138 | cmark_strbuf_putc(html, '>'); |
139 | } else { |
140 | end_heading[3] = (char)('0' + node->as.heading.level); |
141 | cmark_strbuf_puts(html, end_heading); |
142 | cmark_strbuf_puts(html, ">\n" ); |
143 | } |
144 | break; |
145 | |
146 | case CMARK_NODE_CODE_BLOCK: |
147 | cr(html); |
148 | |
149 | if (node->as.code.info == NULL || node->as.code.info[0] == 0) { |
150 | cmark_strbuf_puts(html, "<pre" ); |
151 | S_render_sourcepos(node, html, options); |
152 | cmark_strbuf_puts(html, "><code>" ); |
153 | } else { |
154 | bufsize_t first_tag = 0; |
155 | while (node->as.code.info[first_tag] && |
156 | !cmark_isspace(node->as.code.info[first_tag])) { |
157 | first_tag += 1; |
158 | } |
159 | |
160 | cmark_strbuf_puts(html, "<pre" ); |
161 | S_render_sourcepos(node, html, options); |
162 | cmark_strbuf_puts(html, "><code class=\"language-" ); |
163 | escape_html(html, node->as.code.info, first_tag); |
164 | cmark_strbuf_puts(html, "\">" ); |
165 | } |
166 | |
167 | escape_html(html, node->data, node->len); |
168 | cmark_strbuf_puts(html, "</code></pre>\n" ); |
169 | break; |
170 | |
171 | case CMARK_NODE_HTML_BLOCK: |
172 | cr(html); |
173 | if (!(options & CMARK_OPT_UNSAFE)) { |
174 | cmark_strbuf_puts(html, "<!-- raw HTML omitted -->" ); |
175 | } else { |
176 | cmark_strbuf_put(html, node->data, node->len); |
177 | } |
178 | cr(html); |
179 | break; |
180 | |
181 | case CMARK_NODE_CUSTOM_BLOCK: { |
182 | unsigned char *block = entering ? node->as.custom.on_enter : |
183 | node->as.custom.on_exit; |
184 | cr(html); |
185 | if (block) { |
186 | cmark_strbuf_puts(html, (char *)block); |
187 | } |
188 | cr(html); |
189 | break; |
190 | } |
191 | |
192 | case CMARK_NODE_THEMATIC_BREAK: |
193 | cr(html); |
194 | cmark_strbuf_puts(html, "<hr" ); |
195 | S_render_sourcepos(node, html, options); |
196 | cmark_strbuf_puts(html, " />\n" ); |
197 | break; |
198 | |
199 | case CMARK_NODE_PARAGRAPH: |
200 | parent = cmark_node_parent(node); |
201 | grandparent = cmark_node_parent(parent); |
202 | if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) { |
203 | tight = grandparent->as.list.tight; |
204 | } else { |
205 | tight = false; |
206 | } |
207 | if (!tight) { |
208 | if (entering) { |
209 | cr(html); |
210 | cmark_strbuf_puts(html, "<p" ); |
211 | S_render_sourcepos(node, html, options); |
212 | cmark_strbuf_putc(html, '>'); |
213 | } else { |
214 | cmark_strbuf_puts(html, "</p>\n" ); |
215 | } |
216 | } |
217 | break; |
218 | |
219 | case CMARK_NODE_TEXT: |
220 | escape_html(html, node->data, node->len); |
221 | break; |
222 | |
223 | case CMARK_NODE_LINEBREAK: |
224 | cmark_strbuf_puts(html, "<br />\n" ); |
225 | break; |
226 | |
227 | case CMARK_NODE_SOFTBREAK: |
228 | if (options & CMARK_OPT_HARDBREAKS) { |
229 | cmark_strbuf_puts(html, "<br />\n" ); |
230 | } else if (options & CMARK_OPT_NOBREAKS) { |
231 | cmark_strbuf_putc(html, ' '); |
232 | } else { |
233 | cmark_strbuf_putc(html, '\n'); |
234 | } |
235 | break; |
236 | |
237 | case CMARK_NODE_CODE: |
238 | cmark_strbuf_puts(html, "<code>" ); |
239 | escape_html(html, node->data, node->len); |
240 | cmark_strbuf_puts(html, "</code>" ); |
241 | break; |
242 | |
243 | case CMARK_NODE_HTML_INLINE: |
244 | if (!(options & CMARK_OPT_UNSAFE)) { |
245 | cmark_strbuf_puts(html, "<!-- raw HTML omitted -->" ); |
246 | } else { |
247 | cmark_strbuf_put(html, node->data, node->len); |
248 | } |
249 | break; |
250 | |
251 | case CMARK_NODE_CUSTOM_INLINE: { |
252 | unsigned char *block = entering ? node->as.custom.on_enter : |
253 | node->as.custom.on_exit; |
254 | if (block) { |
255 | cmark_strbuf_puts(html, (char *)block); |
256 | } |
257 | break; |
258 | } |
259 | |
260 | case CMARK_NODE_STRONG: |
261 | if (entering) { |
262 | cmark_strbuf_puts(html, "<strong>" ); |
263 | } else { |
264 | cmark_strbuf_puts(html, "</strong>" ); |
265 | } |
266 | break; |
267 | |
268 | case CMARK_NODE_EMPH: |
269 | if (entering) { |
270 | cmark_strbuf_puts(html, "<em>" ); |
271 | } else { |
272 | cmark_strbuf_puts(html, "</em>" ); |
273 | } |
274 | break; |
275 | |
276 | case CMARK_NODE_LINK: |
277 | if (entering) { |
278 | cmark_strbuf_puts(html, "<a href=\"" ); |
279 | if (node->as.link.url && ((options & CMARK_OPT_UNSAFE) || |
280 | !(_scan_dangerous_url(node->as.link.url)))) { |
281 | houdini_escape_href(html, node->as.link.url, |
282 | strlen((char *)node->as.link.url)); |
283 | } |
284 | if (node->as.link.title) { |
285 | cmark_strbuf_puts(html, "\" title=\"" ); |
286 | escape_html(html, node->as.link.title, |
287 | strlen((char *)node->as.link.title)); |
288 | } |
289 | cmark_strbuf_puts(html, "\">" ); |
290 | } else { |
291 | cmark_strbuf_puts(html, "</a>" ); |
292 | } |
293 | break; |
294 | |
295 | case CMARK_NODE_IMAGE: |
296 | if (entering) { |
297 | cmark_strbuf_puts(html, "<img src=\"" ); |
298 | if (node->as.link.url && ((options & CMARK_OPT_UNSAFE) || |
299 | !(_scan_dangerous_url(node->as.link.url)))) { |
300 | houdini_escape_href(html, node->as.link.url, |
301 | strlen((char *)node->as.link.url)); |
302 | } |
303 | cmark_strbuf_puts(html, "\" alt=\"" ); |
304 | state->plain = node; |
305 | } else { |
306 | if (node->as.link.title) { |
307 | cmark_strbuf_puts(html, "\" title=\"" ); |
308 | escape_html(html, node->as.link.title, |
309 | strlen((char *)node->as.link.title)); |
310 | } |
311 | |
312 | cmark_strbuf_puts(html, "\" />" ); |
313 | } |
314 | break; |
315 | |
316 | default: |
317 | assert(false); |
318 | break; |
319 | } |
320 | |
321 | // cmark_strbuf_putc(html, 'x'); |
322 | return 1; |
323 | } |
324 | |
325 | char *cmark_render_html(cmark_node *root, int options) { |
326 | char *result; |
327 | cmark_strbuf html = CMARK_BUF_INIT(root->mem); |
328 | cmark_event_type ev_type; |
329 | cmark_node *cur; |
330 | struct render_state state = {&html, NULL}; |
331 | cmark_iter *iter = cmark_iter_new(root); |
332 | |
333 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { |
334 | cur = cmark_iter_get_node(iter); |
335 | S_render_node(cur, ev_type, &state, options); |
336 | } |
337 | result = (char *)cmark_strbuf_detach(&html); |
338 | |
339 | cmark_iter_free(iter); |
340 | return result; |
341 | } |
342 | |