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