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