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