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