aboutsummaryrefslogtreecommitdiff
path: root/src/html.c
blob: f719405f8b8e2fa854de06d76c6536aa3bf53c6e (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(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(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(strbuf *html)
  24. {
  25. if (html->size && html->ptr[html->size - 1] != '\n')
  26. strbuf_putc(html, '\n');
  27. }
  28. struct render_state {
  29. 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. strbuf *html = state->html;
  39. char start_header[] = "<h0>";
  40. char end_header[] = "</h0>";
  41. strbuf *info;
  42. bool tight;
  43. bool entering = (ev_type == CMARK_EVENT_ENTER);
  44. if (state->plain == node) { // back at original node
  45. state->plain = NULL;
  46. }
  47. if (state->plain != NULL) {
  48. switch(node->type) {
  49. case CMARK_NODE_TEXT:
  50. case CMARK_NODE_INLINE_CODE:
  51. case CMARK_NODE_INLINE_HTML:
  52. escape_html(html, node->as.literal.data,
  53. node->as.literal.len);
  54. break;
  55. case CMARK_NODE_LINEBREAK:
  56. case CMARK_NODE_SOFTBREAK:
  57. strbuf_putc(html, ' ');
  58. break;
  59. default:
  60. break;
  61. }
  62. return 1;
  63. }
  64. switch (node->type) {
  65. case CMARK_NODE_BLOCK_QUOTE:
  66. if (entering) {
  67. cr(html);
  68. strbuf_puts(html, "<blockquote>\n");
  69. } else {
  70. cr(html);
  71. strbuf_puts(html, "</blockquote>\n");
  72. }
  73. break;
  74. case CMARK_NODE_LIST: {
  75. cmark_list_type list_type = node->as.list.list_type;
  76. int start = node->as.list.start;
  77. if (entering) {
  78. cr(html);
  79. if (list_type == CMARK_BULLET_LIST) {
  80. strbuf_puts(html, "<ul>\n");
  81. }
  82. else if (start == 1) {
  83. strbuf_puts(html, "<ol>\n");
  84. }
  85. else {
  86. strbuf_printf(html, "<ol start=\"%d\">\n",
  87. start);
  88. }
  89. } else {
  90. strbuf_puts(html,
  91. list_type == CMARK_BULLET_LIST ?
  92. "</ul>\n" : "</ol>\n");
  93. }
  94. break;
  95. }
  96. case CMARK_NODE_LIST_ITEM:
  97. if (entering) {
  98. cr(html);
  99. strbuf_puts(html, "<li>");
  100. } else {
  101. strbuf_puts(html, "</li>\n");
  102. }
  103. break;
  104. case CMARK_NODE_HEADER:
  105. if (entering) {
  106. cr(html);
  107. start_header[2] = '0' + node->as.header.level;
  108. strbuf_puts(html, start_header);
  109. } else {
  110. end_header[3] = '0' + node->as.header.level;
  111. strbuf_puts(html, end_header);
  112. strbuf_putc(html, '\n');
  113. }
  114. break;
  115. case CMARK_NODE_CODE_BLOCK:
  116. info = &node->as.code.info;
  117. cr(html);
  118. if (&node->as.code.fence_length == 0
  119. || strbuf_len(info) == 0) {
  120. strbuf_puts(html, "<pre><code>");
  121. }
  122. else {
  123. int first_tag = strbuf_strchr(info, ' ', 0);
  124. if (first_tag < 0)
  125. first_tag = strbuf_len(info);
  126. strbuf_puts(html, "<pre><code class=\"language-");
  127. escape_html(html, info->ptr, first_tag);
  128. strbuf_puts(html, "\">");
  129. }
  130. escape_html(html, node->string_content.ptr, node->string_content.size);
  131. strbuf_puts(html, "</code></pre>\n");
  132. break;
  133. case CMARK_NODE_HTML:
  134. cr(html);
  135. strbuf_put(html, node->string_content.ptr,
  136. node->string_content.size);
  137. break;
  138. case CMARK_NODE_HRULE:
  139. cr(html);
  140. 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. strbuf_puts(html, "<p>");
  155. } else {
  156. 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. strbuf_puts(html, "<br />\n");
  166. break;
  167. case CMARK_NODE_SOFTBREAK:
  168. strbuf_putc(html, '\n');
  169. break;
  170. case CMARK_NODE_INLINE_CODE:
  171. strbuf_puts(html, "<code>");
  172. escape_html(html, node->as.literal.data, node->as.literal.len);
  173. strbuf_puts(html, "</code>");
  174. break;
  175. case CMARK_NODE_INLINE_HTML:
  176. strbuf_put(html, node->as.literal.data, node->as.literal.len);
  177. break;
  178. case CMARK_NODE_STRONG:
  179. if (entering) {
  180. strbuf_puts(html, "<strong>");
  181. } else {
  182. strbuf_puts(html, "</strong>");
  183. }
  184. break;
  185. case CMARK_NODE_EMPH:
  186. if (entering) {
  187. strbuf_puts(html, "<em>");
  188. } else {
  189. strbuf_puts(html, "</em>");
  190. }
  191. break;
  192. case CMARK_NODE_LINK:
  193. if (entering) {
  194. 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. strbuf_puts(html, "\" title=\"");
  199. escape_html(html, node->as.link.title, -1);
  200. }
  201. strbuf_puts(html, "\">");
  202. } else {
  203. strbuf_puts(html, "</a>");
  204. }
  205. break;
  206. case CMARK_NODE_IMAGE:
  207. if (entering) {
  208. strbuf_puts(html, "<img src=\"");
  209. if (node->as.link.url)
  210. escape_href(html, node->as.link.url, -1);
  211. strbuf_puts(html, "\" alt=\"");
  212. state->plain = node;
  213. } else {
  214. if (node->as.link.title) {
  215. strbuf_puts(html, "\" title=\"");
  216. escape_html(html, node->as.link.title, -1);
  217. }
  218. strbuf_puts(html, "\" />");
  219. }
  220. break;
  221. default:
  222. assert(false);
  223. break;
  224. }
  225. // strbuf_putc(html, 'x');
  226. return 1;
  227. }
  228. char *cmark_render_html(cmark_node *root)
  229. {
  230. char *result;
  231. 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 *)strbuf_detach(&html);
  241. cmark_iter_free(iter);
  242. strbuf_free(&html);
  243. return result;
  244. }