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