aboutsummaryrefslogtreecommitdiff
path: root/src/html/html.c
blob: 76d488a474e2442f79e34b49dceba8d901325e54 (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 "debug.h"
  10. #include "html/houdini.h"
  11. typedef struct RenderStack {
  12. struct RenderStack *previous;
  13. const char* literal;
  14. cmark_node* next_sibling;
  15. bool tight;
  16. bool trim;
  17. } render_stack;
  18. static void free_render_stack(render_stack * rstack)
  19. {
  20. render_stack * tempstack;
  21. while (rstack) {
  22. tempstack = rstack;
  23. rstack = rstack->previous;
  24. free(tempstack);
  25. }
  26. }
  27. static render_stack* push_render_stack(render_stack* rstack,
  28. cmark_node* node,
  29. const char* literal)
  30. {
  31. render_stack* newstack;
  32. newstack = (render_stack*)malloc(sizeof(render_stack));
  33. if (newstack == NULL) {
  34. return NULL;
  35. }
  36. newstack->previous = rstack;
  37. newstack->next_sibling = node;
  38. newstack->literal = literal;
  39. newstack->tight = false;
  40. newstack->trim = false;
  41. return newstack;
  42. }
  43. static render_stack* pop_render_stack(render_stack* rstack)
  44. {
  45. render_stack* top = rstack;
  46. if (rstack == NULL) {
  47. return NULL;
  48. }
  49. rstack = rstack->previous;
  50. top->previous = NULL;
  51. free_render_stack(top);
  52. return rstack;
  53. }
  54. // Functions to convert cmark_node and inline lists to HTML strings.
  55. static void escape_html(strbuf *dest, const unsigned char *source, int length)
  56. {
  57. if (length < 0)
  58. length = strlen((char *)source);
  59. houdini_escape_html0(dest, source, (size_t)length, 0);
  60. }
  61. static void escape_href(strbuf *dest, const unsigned char *source, int length)
  62. {
  63. if (length < 0)
  64. length = strlen((char *)source);
  65. houdini_escape_href(dest, source, (size_t)length);
  66. }
  67. static inline void cr(strbuf *html)
  68. {
  69. if (html->size && html->ptr[html->size - 1] != '\n')
  70. strbuf_putc(html, '\n');
  71. }
  72. // Convert an inline list to HTML. Returns 0 on success, and sets result.
  73. static void inlines_to_plain_html(strbuf *html, cmark_node* ils)
  74. {
  75. cmark_node* children;
  76. bool visit_children;
  77. render_stack* rstack = NULL;
  78. while(ils != NULL) {
  79. visit_children = false;
  80. switch(ils->type) {
  81. case NODE_STRING:
  82. case NODE_INLINE_CODE:
  83. case NODE_INLINE_HTML:
  84. escape_html(html, ils->as.literal.data, ils->as.literal.len);
  85. break;
  86. case NODE_LINEBREAK:
  87. case NODE_SOFTBREAK:
  88. strbuf_putc(html, '\n');
  89. break;
  90. case NODE_LINK:
  91. case NODE_IMAGE:
  92. case NODE_STRONG:
  93. case NODE_EMPH:
  94. children = ils->first_child;
  95. visit_children = true;
  96. rstack = push_render_stack(rstack, ils->next, "");
  97. break;
  98. default:
  99. break;
  100. }
  101. if (visit_children) {
  102. ils = children;
  103. } else {
  104. ils = ils->next;
  105. }
  106. while (ils == NULL && rstack != NULL) {
  107. strbuf_puts(html, rstack->literal);
  108. ils = rstack->next_sibling;
  109. rstack = pop_render_stack(rstack);
  110. }
  111. }
  112. free_render_stack(rstack);
  113. }
  114. // Convert an inline list to HTML. Returns 0 on success, and sets result.
  115. static void inlines_to_html(strbuf *html, cmark_node* ils)
  116. {
  117. bool visit_children;
  118. render_stack* rstack = NULL;
  119. while(ils != NULL) {
  120. visit_children = false;
  121. switch(ils->type) {
  122. case NODE_STRING:
  123. escape_html(html, ils->as.literal.data, ils->as.literal.len);
  124. break;
  125. case NODE_LINEBREAK:
  126. strbuf_puts(html, "<br />\n");
  127. break;
  128. case NODE_SOFTBREAK:
  129. strbuf_putc(html, '\n');
  130. break;
  131. case NODE_INLINE_CODE:
  132. strbuf_puts(html, "<code>");
  133. escape_html(html, ils->as.literal.data, ils->as.literal.len);
  134. strbuf_puts(html, "</code>");
  135. break;
  136. case NODE_INLINE_HTML:
  137. strbuf_put(html,
  138. ils->as.literal.data,
  139. ils->as.literal.len);
  140. break;
  141. case NODE_LINK:
  142. strbuf_puts(html, "<a href=\"");
  143. if (ils->as.link.url)
  144. escape_href(html, ils->as.link.url, -1);
  145. if (ils->as.link.title) {
  146. strbuf_puts(html, "\" title=\"");
  147. escape_html(html, ils->as.link.title, -1);
  148. }
  149. strbuf_puts(html, "\">");
  150. visit_children = true;
  151. rstack = push_render_stack(rstack, ils->next, "</a>");
  152. break;
  153. case NODE_IMAGE:
  154. strbuf_puts(html, "<img src=\"");
  155. if (ils->as.link.url)
  156. escape_href(html, ils->as.link.url, -1);
  157. strbuf_puts(html, "\" alt=\"");
  158. inlines_to_plain_html(html, ils->first_child);
  159. if (ils->as.link.title) {
  160. strbuf_puts(html, "\" title=\"");
  161. escape_html(html, ils->as.link.title, -1);
  162. }
  163. strbuf_puts(html, "\" />");
  164. break;
  165. case NODE_STRONG:
  166. strbuf_puts(html, "<strong>");
  167. visit_children = true;
  168. rstack = push_render_stack(rstack, ils->next, "</strong>");
  169. break;
  170. case NODE_EMPH:
  171. strbuf_puts(html, "<em>");
  172. visit_children = true;
  173. rstack = push_render_stack(rstack, ils->next, "</em>");
  174. break;
  175. default:
  176. break;
  177. }
  178. if (visit_children) {
  179. ils = ils->first_child;
  180. } else {
  181. ils = ils->next;
  182. }
  183. while (ils == NULL && rstack != NULL) {
  184. strbuf_puts(html, rstack->literal);
  185. ils = rstack->next_sibling;
  186. rstack = pop_render_stack(rstack);
  187. }
  188. }
  189. free_render_stack(rstack);
  190. }
  191. // Convert a cmark_node list to HTML. Returns 0 on success, and sets result.
  192. static void blocks_to_html(strbuf *html, cmark_node *b)
  193. {
  194. cmark_list *data;
  195. render_stack* rstack = NULL;
  196. bool visit_children = false;
  197. bool tight = false;
  198. while(b != NULL) {
  199. visit_children = false;
  200. switch(b->type) {
  201. case NODE_DOCUMENT:
  202. rstack = push_render_stack(rstack, b->next, "");
  203. rstack->tight = false;
  204. rstack->trim = false;
  205. visit_children = true;
  206. break;
  207. case NODE_PARAGRAPH:
  208. if (tight) {
  209. inlines_to_html(html, b->first_child);
  210. } else {
  211. cr(html);
  212. strbuf_puts(html, "<p>");
  213. inlines_to_html(html, b->first_child);
  214. strbuf_puts(html, "</p>\n");
  215. }
  216. break;
  217. case NODE_BQUOTE:
  218. cr(html);
  219. strbuf_puts(html, "<blockquote>\n");
  220. rstack = push_render_stack(rstack, b->next, "</blockquote>\n");
  221. rstack->tight = tight;
  222. rstack->trim = false;
  223. tight = false;
  224. visit_children = true;
  225. break;
  226. case NODE_LIST_ITEM:
  227. cr(html);
  228. strbuf_puts(html, "<li>");
  229. rstack = push_render_stack(rstack, b->next, "</li>\n");
  230. rstack->tight = tight;
  231. rstack->trim = true;
  232. visit_children = true;
  233. break;
  234. case NODE_LIST:
  235. // make sure a list starts at the beginning of the line:
  236. cr(html);
  237. data = &(b->as.list);
  238. if (data->start > 1) {
  239. strbuf_printf(html, "<%s start=\"%d\">\n",
  240. data->list_type == CMARK_BULLET_LIST ? "ul" : "ol",
  241. data->start);
  242. } else {
  243. strbuf_puts(html, data->list_type == CMARK_BULLET_LIST ? "<ul>\n" : "<ol>\n");
  244. }
  245. rstack = push_render_stack(rstack, b->next,
  246. data->list_type == CMARK_BULLET_LIST ?
  247. "\n</ul>\n" : "\n</ol>\n");
  248. rstack->tight = tight;
  249. rstack->trim = false;
  250. tight = data->tight;
  251. visit_children = true;
  252. break;
  253. case NODE_ATX_HEADER:
  254. case NODE_SETEXT_HEADER:
  255. cr(html);
  256. strbuf_printf(html, "<h%d>", b->as.header.level);
  257. inlines_to_html(html, b->first_child);
  258. strbuf_printf(html, "</h%d>\n", b->as.header.level);
  259. break;
  260. case NODE_INDENTED_CODE:
  261. case NODE_FENCED_CODE:
  262. cr(html);
  263. strbuf_puts(html, "<pre><code");
  264. if (b->type == NODE_FENCED_CODE) {
  265. strbuf *info = &b->as.code.info;
  266. if (strbuf_len(info) > 0) {
  267. int first_tag = strbuf_strchr(info, ' ', 0);
  268. if (first_tag < 0)
  269. first_tag = strbuf_len(info);
  270. strbuf_puts(html, " class=\"language-");
  271. escape_html(html, info->ptr, first_tag);
  272. strbuf_putc(html, '"');
  273. }
  274. }
  275. strbuf_putc(html, '>');
  276. escape_html(html, b->string_content.ptr, b->string_content.size);
  277. strbuf_puts(html, "</code></pre>\n");
  278. break;
  279. case NODE_HTML:
  280. strbuf_put(html, b->string_content.ptr, b->string_content.size);
  281. break;
  282. case NODE_HRULE:
  283. strbuf_puts(html, "<hr />\n");
  284. break;
  285. case NODE_REFERENCE_DEF:
  286. break;
  287. default:
  288. assert(false);
  289. }
  290. if (visit_children) {
  291. b = b->first_child;
  292. } else {
  293. b = b->next;
  294. }
  295. while (b == NULL && rstack != NULL) {
  296. strbuf_puts(html, rstack->literal);
  297. if (rstack->trim) {
  298. strbuf_rtrim(html);
  299. }
  300. tight = rstack->tight;
  301. b = rstack->next_sibling;
  302. rstack = pop_render_stack(rstack);
  303. }
  304. }
  305. free_render_stack(rstack);
  306. }
  307. unsigned char *cmark_render_html(cmark_node *root)
  308. {
  309. unsigned char *result;
  310. strbuf html = GH_BUF_INIT;
  311. blocks_to_html(&html, root);
  312. result = strbuf_detach(&html);
  313. strbuf_free(&html);
  314. return result;
  315. }