aboutsummaryrefslogtreecommitdiff
path: root/src/html.c
blob: b5a09504bf4cb27abaafbb7823c2d67dacd5cd3f (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 int
  33. S_render_node(cmark_node *node, cmark_event_type ev_type, void *vstate)
  34. {
  35. struct render_state *state = vstate;
  36. cmark_node *parent;
  37. cmark_node *grandparent;
  38. cmark_strbuf *html = state->html;
  39. char start_header[] = "<h0>";
  40. char end_header[] = "</h0>";
  41. bool tight;
  42. bool entering = (ev_type == CMARK_EVENT_ENTER);
  43. if (state->plain == node) { // back at original node
  44. state->plain = NULL;
  45. }
  46. if (state->plain != NULL) {
  47. switch(node->type) {
  48. case CMARK_NODE_TEXT:
  49. case CMARK_NODE_CODE:
  50. case CMARK_NODE_INLINE_HTML:
  51. escape_html(html, node->as.literal.data,
  52. node->as.literal.len);
  53. break;
  54. case CMARK_NODE_LINEBREAK:
  55. case CMARK_NODE_SOFTBREAK:
  56. cmark_strbuf_putc(html, ' ');
  57. break;
  58. default:
  59. break;
  60. }
  61. return 1;
  62. }
  63. switch (node->type) {
  64. case CMARK_NODE_DOCUMENT:
  65. break;
  66. case CMARK_NODE_BLOCK_QUOTE:
  67. if (entering) {
  68. cr(html);
  69. cmark_strbuf_puts(html, "<blockquote>\n");
  70. } else {
  71. cr(html);
  72. cmark_strbuf_puts(html, "</blockquote>\n");
  73. }
  74. break;
  75. case CMARK_NODE_LIST: {
  76. cmark_list_type list_type = node->as.list.list_type;
  77. int start = node->as.list.start;
  78. if (entering) {
  79. cr(html);
  80. if (list_type == CMARK_BULLET_LIST) {
  81. cmark_strbuf_puts(html, "<ul>\n");
  82. }
  83. else if (start == 1) {
  84. cmark_strbuf_puts(html, "<ol>\n");
  85. }
  86. else {
  87. cmark_strbuf_printf(html, "<ol start=\"%d\">\n",
  88. start);
  89. }
  90. } else {
  91. cmark_strbuf_puts(html,
  92. list_type == CMARK_BULLET_LIST ?
  93. "</ul>\n" : "</ol>\n");
  94. }
  95. break;
  96. }
  97. case CMARK_NODE_LIST_ITEM:
  98. if (entering) {
  99. cr(html);
  100. cmark_strbuf_puts(html, "<li>");
  101. } else {
  102. cmark_strbuf_puts(html, "</li>\n");
  103. }
  104. break;
  105. case CMARK_NODE_HEADER:
  106. if (entering) {
  107. cr(html);
  108. start_header[2] = '0' + node->as.header.level;
  109. cmark_strbuf_puts(html, start_header);
  110. } else {
  111. end_header[3] = '0' + node->as.header.level;
  112. cmark_strbuf_puts(html, end_header);
  113. cmark_strbuf_putc(html, '\n');
  114. }
  115. break;
  116. case CMARK_NODE_CODE_BLOCK:
  117. cr(html);
  118. if (!node->as.code.fenced || node->as.code.info.len == 0) {
  119. cmark_strbuf_puts(html, "<pre><code>");
  120. }
  121. else {
  122. int first_tag = 0;
  123. while (first_tag < node->as.code.info.len &&
  124. node->as.code.info.data[first_tag] != ' ') {
  125. first_tag += 1;
  126. }
  127. cmark_strbuf_puts(html, "<pre><code class=\"language-");
  128. escape_html(html, node->as.code.info.data, first_tag);
  129. cmark_strbuf_puts(html, "\">");
  130. }
  131. escape_html(html, node->as.literal.data, node->as.literal.len);
  132. cmark_strbuf_puts(html, "</code></pre>\n");
  133. break;
  134. case CMARK_NODE_HTML:
  135. cr(html);
  136. cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
  137. break;
  138. case CMARK_NODE_HRULE:
  139. cr(html);
  140. cmark_strbuf_puts(html, "<hr />\n");
  141. break;
  142. case CMARK_NODE_PARAGRAPH:
  143. parent = cmark_node_parent(node);
  144. grandparent = cmark_node_parent(parent);
  145. if (grandparent != NULL &&
  146. grandparent->type == CMARK_NODE_LIST) {
  147. tight = grandparent->as.list.tight;
  148. } else {
  149. tight = false;
  150. }
  151. if (!tight) {
  152. if (entering) {
  153. cr(html);
  154. cmark_strbuf_puts(html, "<p>");
  155. } else {
  156. cmark_strbuf_puts(html, "</p>\n");
  157. }
  158. }
  159. break;
  160. case CMARK_NODE_TEXT:
  161. escape_html(html, node->as.literal.data,
  162. node->as.literal.len);
  163. break;
  164. case CMARK_NODE_LINEBREAK:
  165. cmark_strbuf_puts(html, "<br />\n");
  166. break;
  167. case CMARK_NODE_SOFTBREAK:
  168. cmark_strbuf_putc(html, '\n');
  169. break;
  170. case CMARK_NODE_CODE:
  171. cmark_strbuf_puts(html, "<code>");
  172. escape_html(html, node->as.literal.data, node->as.literal.len);
  173. cmark_strbuf_puts(html, "</code>");
  174. break;
  175. case CMARK_NODE_INLINE_HTML:
  176. cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
  177. break;
  178. case CMARK_NODE_STRONG:
  179. if (entering) {
  180. cmark_strbuf_puts(html, "<strong>");
  181. } else {
  182. cmark_strbuf_puts(html, "</strong>");
  183. }
  184. break;
  185. case CMARK_NODE_EMPH:
  186. if (entering) {
  187. cmark_strbuf_puts(html, "<em>");
  188. } else {
  189. cmark_strbuf_puts(html, "</em>");
  190. }
  191. break;
  192. case CMARK_NODE_LINK:
  193. if (entering) {
  194. cmark_strbuf_puts(html, "<a href=\"");
  195. if (node->as.link.url)
  196. escape_href(html, node->as.link.url, -1);
  197. if (node->as.link.title) {
  198. cmark_strbuf_puts(html, "\" title=\"");
  199. escape_html(html, node->as.link.title, -1);
  200. }
  201. cmark_strbuf_puts(html, "\">");
  202. } else {
  203. cmark_strbuf_puts(html, "</a>");
  204. }
  205. break;
  206. case CMARK_NODE_IMAGE:
  207. if (entering) {
  208. cmark_strbuf_puts(html, "<img src=\"");
  209. if (node->as.link.url)
  210. escape_href(html, node->as.link.url, -1);
  211. cmark_strbuf_puts(html, "\" alt=\"");
  212. state->plain = node;
  213. } else {
  214. if (node->as.link.title) {
  215. cmark_strbuf_puts(html, "\" title=\"");
  216. escape_html(html, node->as.link.title, -1);
  217. }
  218. cmark_strbuf_puts(html, "\" />");
  219. }
  220. break;
  221. default:
  222. assert(false);
  223. break;
  224. }
  225. // cmark_strbuf_putc(html, 'x');
  226. return 1;
  227. }
  228. char *cmark_render_html(cmark_node *root)
  229. {
  230. char *result;
  231. cmark_strbuf html = GH_BUF_INIT;
  232. cmark_event_type ev_type;
  233. cmark_node *cur;
  234. struct render_state state = { &html, NULL };
  235. cmark_iter *iter = cmark_iter_new(root);
  236. while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
  237. cur = cmark_iter_get_node(iter);
  238. S_render_node(cur, ev_type, &state);
  239. }
  240. result = (char *)cmark_strbuf_detach(&html);
  241. cmark_iter_free(iter);
  242. cmark_strbuf_free(&html);
  243. return result;
  244. }