aboutsummaryrefslogtreecommitdiff
path: root/src/html.c
blob: 8ccb495b5db774ce6d5781170a553f5f0955bdc9 (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. {
  35. if (CMARK_OPT_SOURCEPOS & options) {
  36. cmark_strbuf_printf(html, " data-sourcepos=\"%d:%d-%d:%d\"",
  37. cmark_node_get_start_line(node),
  38. cmark_node_get_start_column(node),
  39. cmark_node_get_end_line(node),
  40. cmark_node_get_end_column(node));
  41. }
  42. }
  43. static int
  44. S_render_node(cmark_node *node, cmark_event_type ev_type,
  45. struct render_state *state, long options)
  46. {
  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. } else if (start == 1) {
  98. cmark_strbuf_puts(html, "<ol");
  99. S_render_sourcepos(node, html, options);
  100. cmark_strbuf_puts(html, ">\n");
  101. } else {
  102. cmark_strbuf_printf(html,
  103. "<ol start=\"%d\"",
  104. start);
  105. S_render_sourcepos(node, html, options);
  106. cmark_strbuf_puts(html, ">\n");
  107. }
  108. } else {
  109. cmark_strbuf_puts(html,
  110. list_type == CMARK_BULLET_LIST ?
  111. "</ul>\n" : "</ol>\n");
  112. }
  113. break;
  114. }
  115. case CMARK_NODE_ITEM:
  116. if (entering) {
  117. cr(html);
  118. cmark_strbuf_puts(html, "<li");
  119. S_render_sourcepos(node, html, options);
  120. cmark_strbuf_putc(html, '>');
  121. } else {
  122. cmark_strbuf_puts(html, "</li>\n");
  123. }
  124. break;
  125. case CMARK_NODE_HEADER:
  126. if (entering) {
  127. cr(html);
  128. start_header[2] = '0' + node->as.header.level;
  129. cmark_strbuf_puts(html, start_header);
  130. S_render_sourcepos(node, html, options);
  131. cmark_strbuf_putc(html, '>');
  132. } else {
  133. end_header[3] = '0' + node->as.header.level;
  134. cmark_strbuf_puts(html, end_header);
  135. cmark_strbuf_puts(html, ">\n");
  136. }
  137. break;
  138. case CMARK_NODE_CODE_BLOCK:
  139. cr(html);
  140. if (!node->as.code.fenced || node->as.code.info.len == 0) {
  141. cmark_strbuf_puts(html, "<pre");
  142. S_render_sourcepos(node, html, options);
  143. cmark_strbuf_puts(html, "><code>");
  144. } else {
  145. int first_tag = 0;
  146. while (first_tag < node->as.code.info.len &&
  147. node->as.code.info.data[first_tag] != ' ') {
  148. first_tag += 1;
  149. }
  150. cmark_strbuf_puts(html, "<pre");
  151. S_render_sourcepos(node, html, options);
  152. cmark_strbuf_puts(html, "><code class=\"language-");
  153. escape_html(html, node->as.code.info.data, first_tag);
  154. cmark_strbuf_puts(html, "\">");
  155. }
  156. escape_html(html, node->as.code.literal.data,
  157. node->as.code.literal.len);
  158. cmark_strbuf_puts(html, "</code></pre>\n");
  159. break;
  160. case CMARK_NODE_HTML:
  161. cr(html);
  162. cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
  163. break;
  164. case CMARK_NODE_HRULE:
  165. cr(html);
  166. cmark_strbuf_puts(html, "<hr");
  167. S_render_sourcepos(node, html, options);
  168. cmark_strbuf_puts(html, " />\n");
  169. break;
  170. case CMARK_NODE_PARAGRAPH:
  171. parent = cmark_node_parent(node);
  172. grandparent = cmark_node_parent(parent);
  173. if (grandparent != NULL &&
  174. grandparent->type == CMARK_NODE_LIST) {
  175. tight = grandparent->as.list.tight;
  176. } else {
  177. tight = false;
  178. }
  179. if (!tight) {
  180. if (entering) {
  181. cr(html);
  182. cmark_strbuf_puts(html, "<p");
  183. S_render_sourcepos(node, html, options);
  184. cmark_strbuf_putc(html, '>');
  185. } else {
  186. cmark_strbuf_puts(html, "</p>\n");
  187. }
  188. }
  189. break;
  190. case CMARK_NODE_TEXT:
  191. escape_html(html, node->as.literal.data,
  192. node->as.literal.len);
  193. break;
  194. case CMARK_NODE_LINEBREAK:
  195. cmark_strbuf_puts(html, "<br />\n");
  196. break;
  197. case CMARK_NODE_SOFTBREAK:
  198. if (options & CMARK_OPT_HARDBREAKS) {
  199. cmark_strbuf_puts(html, "<br />\n");
  200. } else {
  201. cmark_strbuf_putc(html, '\n');
  202. }
  203. break;
  204. case CMARK_NODE_CODE:
  205. cmark_strbuf_puts(html, "<code>");
  206. escape_html(html, node->as.literal.data, node->as.literal.len);
  207. cmark_strbuf_puts(html, "</code>");
  208. break;
  209. case CMARK_NODE_INLINE_HTML:
  210. cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
  211. break;
  212. case CMARK_NODE_STRONG:
  213. if (entering) {
  214. cmark_strbuf_puts(html, "<strong>");
  215. } else {
  216. cmark_strbuf_puts(html, "</strong>");
  217. }
  218. break;
  219. case CMARK_NODE_EMPH:
  220. if (entering) {
  221. cmark_strbuf_puts(html, "<em>");
  222. } else {
  223. cmark_strbuf_puts(html, "</em>");
  224. }
  225. break;
  226. case CMARK_NODE_LINK:
  227. if (entering) {
  228. cmark_strbuf_puts(html, "<a href=\"");
  229. if (node->as.link.url)
  230. escape_href(html, node->as.link.url, -1);
  231. if (node->as.link.title) {
  232. cmark_strbuf_puts(html, "\" title=\"");
  233. escape_html(html, node->as.link.title, -1);
  234. }
  235. cmark_strbuf_puts(html, "\">");
  236. } else {
  237. cmark_strbuf_puts(html, "</a>");
  238. }
  239. break;
  240. case CMARK_NODE_IMAGE:
  241. if (entering) {
  242. cmark_strbuf_puts(html, "<img src=\"");
  243. if (node->as.link.url)
  244. escape_href(html, node->as.link.url, -1);
  245. cmark_strbuf_puts(html, "\" alt=\"");
  246. state->plain = node;
  247. } else {
  248. if (node->as.link.title) {
  249. cmark_strbuf_puts(html, "\" title=\"");
  250. escape_html(html, node->as.link.title, -1);
  251. }
  252. cmark_strbuf_puts(html, "\" />");
  253. }
  254. break;
  255. default:
  256. assert(false);
  257. break;
  258. }
  259. // cmark_strbuf_putc(html, 'x');
  260. return 1;
  261. }
  262. char *cmark_render_html(cmark_node *root, long options)
  263. {
  264. char *result;
  265. cmark_strbuf html = GH_BUF_INIT;
  266. cmark_event_type ev_type;
  267. cmark_node *cur;
  268. struct render_state state = { &html, NULL };
  269. cmark_iter *iter = cmark_iter_new(root);
  270. while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
  271. cur = cmark_iter_get_node(iter);
  272. S_render_node(cur, ev_type, &state, options);
  273. }
  274. result = (char *)cmark_strbuf_detach(&html);
  275. cmark_iter_free(iter);
  276. return result;
  277. }