aboutsummaryrefslogtreecommitdiff
path: root/src/html/html.c
blob: 196949087cf4c28008ab402adb13d78aea77613f (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 "buffer.h"
  8. #include "ast.h"
  9. #include "debug.h"
  10. #include "html/houdini.h"
  11. typedef struct RenderStack {
  12. struct RenderStack *previous;
  13. char* literal;
  14. union {
  15. node_inl *inl;
  16. node_block *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. node_inl* 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. node_block* 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 node_block 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, node_inl* ils)
  95. {
  96. node_inl* children;
  97. bool visit_children;
  98. render_stack* rstack = NULL;
  99. while(ils != NULL) {
  100. visit_children = false;
  101. switch(ils->tag) {
  102. case INL_STRING:
  103. case INL_CODE:
  104. case INL_RAW_HTML:
  105. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  106. break;
  107. case INL_LINEBREAK:
  108. case INL_SOFTBREAK:
  109. strbuf_putc(html, '\n');
  110. break;
  111. case INL_LINK:
  112. case INL_IMAGE:
  113. children = ils->content.linkable.label;
  114. visit_children = true;
  115. rstack = push_inline(rstack, ils->next, "");
  116. break;
  117. case INL_STRONG:
  118. case INL_EMPH:
  119. children = ils->content.inlines;
  120. visit_children = true;
  121. rstack = push_inline(rstack, ils->next, "");
  122. break;
  123. }
  124. if (visit_children) {
  125. ils = children;
  126. } else {
  127. ils = ils->next;
  128. }
  129. while (ils == NULL && rstack != NULL) {
  130. strbuf_puts(html, rstack->literal);
  131. ils = rstack->next_sibling.inl;
  132. rstack = pop_render_stack(rstack);
  133. }
  134. }
  135. free_render_stack(rstack);
  136. }
  137. // Convert an inline list to HTML. Returns 0 on success, and sets result.
  138. static void inlines_to_html(strbuf *html, node_inl* ils)
  139. {
  140. node_inl* children;
  141. render_stack* rstack = NULL;
  142. while(ils != NULL) {
  143. children = NULL;
  144. switch(ils->tag) {
  145. case INL_STRING:
  146. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  147. break;
  148. case INL_LINEBREAK:
  149. strbuf_puts(html, "<br />\n");
  150. break;
  151. case INL_SOFTBREAK:
  152. strbuf_putc(html, '\n');
  153. break;
  154. case INL_CODE:
  155. strbuf_puts(html, "<code>");
  156. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  157. strbuf_puts(html, "</code>");
  158. break;
  159. case INL_RAW_HTML:
  160. strbuf_put(html,
  161. ils->content.literal.data,
  162. ils->content.literal.len);
  163. break;
  164. case INL_LINK:
  165. strbuf_puts(html, "<a href=\"");
  166. if (ils->content.linkable.url)
  167. escape_href(html, ils->content.linkable.url, -1);
  168. if (ils->content.linkable.title) {
  169. strbuf_puts(html, "\" title=\"");
  170. escape_html(html, ils->content.linkable.title, -1);
  171. }
  172. strbuf_puts(html, "\">");
  173. children = ils->content.linkable.label;
  174. rstack = push_inline(rstack, ils->next, "</a>");
  175. break;
  176. case INL_IMAGE:
  177. strbuf_puts(html, "<img src=\"");
  178. if (ils->content.linkable.url)
  179. escape_href(html, ils->content.linkable.url, -1);
  180. strbuf_puts(html, "\" alt=\"");
  181. inlines_to_plain_html(html, ils->content.inlines);
  182. if (ils->content.linkable.title) {
  183. strbuf_puts(html, "\" title=\"");
  184. escape_html(html, ils->content.linkable.title, -1);
  185. }
  186. strbuf_puts(html, "\"/>");
  187. break;
  188. case INL_STRONG:
  189. strbuf_puts(html, "<strong>");
  190. children = ils->content.inlines;
  191. rstack = push_inline(rstack, ils->next, "</strong>");
  192. break;
  193. case INL_EMPH:
  194. strbuf_puts(html, "<em>");
  195. children = ils->content.inlines;
  196. rstack = push_inline(rstack, ils->next, "</em>");
  197. break;
  198. }
  199. if (children) {
  200. ils = children;
  201. } else {
  202. ils = ils->next;
  203. }
  204. while (ils == NULL && rstack != NULL) {
  205. strbuf_puts(html, rstack->literal);
  206. ils = rstack->next_sibling.inl;
  207. rstack = pop_render_stack(rstack);
  208. }
  209. }
  210. free_render_stack(rstack);
  211. }
  212. // Convert a node_block list to HTML. Returns 0 on success, and sets result.
  213. static void blocks_to_html(strbuf *html, node_block *b)
  214. {
  215. struct ListData *data;
  216. render_stack* rstack = NULL;
  217. bool visit_children = false;
  218. bool tight = false;
  219. while(b != NULL) {
  220. visit_children = false;
  221. switch(b->tag) {
  222. case BLOCK_DOCUMENT:
  223. rstack = push_block(rstack, b->next, "", false, false);
  224. visit_children = true;
  225. break;
  226. case BLOCK_PARAGRAPH:
  227. if (tight) {
  228. inlines_to_html(html, b->inline_content);
  229. } else {
  230. cr(html);
  231. strbuf_puts(html, "<p>");
  232. inlines_to_html(html, b->inline_content);
  233. strbuf_puts(html, "</p>\n");
  234. }
  235. break;
  236. case BLOCK_BQUOTE:
  237. cr(html);
  238. strbuf_puts(html, "<blockquote>\n");
  239. rstack = push_block(rstack, b->next, "</blockquote>\n", tight, false);
  240. tight = false;
  241. visit_children = true;
  242. break;
  243. case BLOCK_LIST_ITEM:
  244. cr(html);
  245. strbuf_puts(html, "<li>");
  246. rstack = push_block(rstack, b->next, "</li>\n", tight, true);
  247. visit_children = true;
  248. break;
  249. case BLOCK_LIST:
  250. // make sure a list starts at the beginning of the line:
  251. cr(html);
  252. data = &(b->as.list);
  253. if (data->start > 1) {
  254. strbuf_printf(html, "<%s start=\"%d\">\n",
  255. data->list_type == CMARK_BULLET_LIST ? "ul" : "ol",
  256. data->start);
  257. } else {
  258. strbuf_puts(html, data->list_type == CMARK_BULLET_LIST ? "<ul>\n" : "<ol>\n");
  259. }
  260. rstack = push_block(rstack, b->next,
  261. data->list_type == CMARK_BULLET_LIST ?
  262. "\n</ul>\n" : "\n</ol>\n", tight, false);
  263. tight = data->tight;
  264. visit_children = true;
  265. break;
  266. case BLOCK_ATX_HEADER:
  267. case BLOCK_SETEXT_HEADER:
  268. cr(html);
  269. strbuf_printf(html, "<h%d>", b->as.header.level);
  270. inlines_to_html(html, b->inline_content);
  271. strbuf_printf(html, "</h%d>\n", b->as.header.level);
  272. break;
  273. case BLOCK_INDENTED_CODE:
  274. case BLOCK_FENCED_CODE:
  275. cr(html);
  276. strbuf_puts(html, "<pre><code");
  277. if (b->tag == BLOCK_FENCED_CODE) {
  278. strbuf *info = &b->as.code.info;
  279. if (strbuf_len(info) > 0) {
  280. int first_tag = strbuf_strchr(info, ' ', 0);
  281. if (first_tag < 0)
  282. first_tag = strbuf_len(info);
  283. strbuf_puts(html, " class=\"language-");
  284. escape_html(html, info->ptr, first_tag);
  285. strbuf_putc(html, '"');
  286. }
  287. }
  288. strbuf_putc(html, '>');
  289. escape_html(html, b->string_content.ptr, b->string_content.size);
  290. strbuf_puts(html, "</code></pre>\n");
  291. break;
  292. case BLOCK_HTML:
  293. strbuf_put(html, b->string_content.ptr, b->string_content.size);
  294. break;
  295. case BLOCK_HRULE:
  296. strbuf_puts(html, "<hr />\n");
  297. break;
  298. case BLOCK_REFERENCE_DEF:
  299. break;
  300. default:
  301. assert(false);
  302. }
  303. if (visit_children) {
  304. b = b->children;
  305. } else {
  306. b = b->next;
  307. }
  308. while (b == NULL && rstack != NULL) {
  309. strbuf_puts(html, rstack->literal);
  310. if (rstack->trim) {
  311. strbuf_rtrim(html);
  312. }
  313. tight = rstack->tight;
  314. b = rstack->next_sibling.block;
  315. rstack = pop_render_stack(rstack);
  316. }
  317. }
  318. free_render_stack(rstack);
  319. }
  320. unsigned char *cmark_render_html(node_block *root)
  321. {
  322. unsigned char *result;
  323. strbuf html = GH_BUF_INIT;
  324. blocks_to_html(&html, root);
  325. result = strbuf_detach(&html);
  326. strbuf_free(&html);
  327. return result;
  328. }