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