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