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