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