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