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