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