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