aboutsummaryrefslogtreecommitdiff
path: root/src/html/html.c
blob: ab6fc35412aa2a81626b6be5ebbc6e42c4b5eb2c (plain)
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <stdbool.h>
  4. #include <string.h>
  5. #include <assert.h>
  6. #include "stmd.h"
  7. #include "debug.h"
  8. #include "html/houdini.h"
  9. // Functions to convert node_block and inline lists to HTML strings.
  10. static void escape_html(strbuf *dest, const unsigned char *source, int length)
  11. {
  12. if (length < 0)
  13. length = strlen((char *)source);
  14. houdini_escape_html0(dest, source, (size_t)length, 0);
  15. }
  16. static void escape_href(strbuf *dest, const unsigned char *source, int length)
  17. {
  18. if (length < 0)
  19. length = strlen((char *)source);
  20. houdini_escape_href(dest, source, (size_t)length);
  21. }
  22. static inline void cr(strbuf *html)
  23. {
  24. if (html->size && html->ptr[html->size - 1] != '\n')
  25. strbuf_putc(html, '\n');
  26. }
  27. // Convert an inline list to HTML. Returns 0 on success, and sets result.
  28. static void inlines_to_html(strbuf *html, node_inl* ils)
  29. {
  30. strbuf scrap = GH_BUF_INIT;
  31. while(ils != NULL) {
  32. switch(ils->tag) {
  33. case INL_STRING:
  34. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  35. break;
  36. case INL_LINEBREAK:
  37. strbuf_puts(html, "<br />\n");
  38. break;
  39. case INL_SOFTBREAK:
  40. strbuf_putc(html, '\n');
  41. break;
  42. case INL_CODE:
  43. strbuf_puts(html, "<code>");
  44. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  45. strbuf_puts(html, "</code>");
  46. break;
  47. case INL_RAW_HTML:
  48. strbuf_put(html,
  49. ils->content.literal.data,
  50. ils->content.literal.len);
  51. break;
  52. case INL_LINK:
  53. strbuf_puts(html, "<a href=\"");
  54. if (ils->content.linkable.url)
  55. escape_href(html, ils->content.linkable.url, -1);
  56. if (ils->content.linkable.title) {
  57. strbuf_puts(html, "\" title=\"");
  58. escape_html(html, ils->content.linkable.title, -1);
  59. }
  60. strbuf_puts(html, "\">");
  61. inlines_to_html(html, ils->content.inlines);
  62. strbuf_puts(html, "</a>");
  63. break;
  64. case INL_IMAGE:
  65. strbuf_puts(html, "<img src=\"");
  66. if (ils->content.linkable.url)
  67. escape_href(html, ils->content.linkable.url, -1);
  68. inlines_to_html(&scrap, ils->content.inlines);
  69. strbuf_puts(html, "\" alt=\"");
  70. if (scrap.size)
  71. escape_html(html, scrap.ptr, scrap.size);
  72. strbuf_clear(&scrap);
  73. if (ils->content.linkable.title) {
  74. strbuf_puts(html, "\" title=\"");
  75. escape_html(html, ils->content.linkable.title, -1);
  76. }
  77. strbuf_puts(html, "\"/>");
  78. break;
  79. case INL_STRONG:
  80. strbuf_puts(html, "<strong>");
  81. inlines_to_html(html, ils->content.inlines);
  82. strbuf_puts(html, "</strong>");
  83. break;
  84. case INL_EMPH:
  85. strbuf_puts(html, "<em>");
  86. inlines_to_html(html, ils->content.inlines);
  87. strbuf_puts(html, "</em>");
  88. break;
  89. }
  90. ils = ils->next;
  91. }
  92. strbuf_free(&scrap);
  93. }
  94. // Convert a node_block list to HTML. Returns 0 on success, and sets result.
  95. static void blocks_to_html(strbuf *html, node_block *b, bool tight)
  96. {
  97. struct ListData *data;
  98. while(b != NULL) {
  99. switch(b->tag) {
  100. case BLOCK_DOCUMENT:
  101. blocks_to_html(html, b->children, false);
  102. break;
  103. case BLOCK_PARAGRAPH:
  104. if (tight) {
  105. inlines_to_html(html, b->inline_content);
  106. } else {
  107. cr(html);
  108. strbuf_puts(html, "<p>");
  109. inlines_to_html(html, b->inline_content);
  110. strbuf_puts(html, "</p>\n");
  111. }
  112. break;
  113. case BLOCK_BQUOTE:
  114. cr(html);
  115. strbuf_puts(html, "<blockquote>\n");
  116. blocks_to_html(html, b->children, false);
  117. strbuf_puts(html, "</blockquote>\n");
  118. break;
  119. case BLOCK_LIST_ITEM:
  120. cr(html);
  121. strbuf_puts(html, "<li>");
  122. blocks_to_html(html, b->children, tight);
  123. strbuf_trim(html); /* TODO: rtrim */
  124. strbuf_puts(html, "</li>\n");
  125. break;
  126. case BLOCK_LIST:
  127. // make sure a list starts at the beginning of the line:
  128. cr(html);
  129. data = &(b->as.list);
  130. if (data->start > 1) {
  131. strbuf_printf(html, "<%s start=\"%d\">\n",
  132. data->list_type == bullet ? "ul" : "ol",
  133. data->start);
  134. } else {
  135. strbuf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
  136. }
  137. blocks_to_html(html, b->children, data->tight);
  138. strbuf_puts(html, data->list_type == bullet ? "</ul>" : "</ol>");
  139. strbuf_putc(html, '\n');
  140. break;
  141. case BLOCK_ATX_HEADER:
  142. case BLOCK_SETEXT_HEADER:
  143. cr(html);
  144. strbuf_printf(html, "<h%d>", b->as.header.level);
  145. inlines_to_html(html, b->inline_content);
  146. strbuf_printf(html, "</h%d>\n", b->as.header.level);
  147. break;
  148. case BLOCK_INDENTED_CODE:
  149. case BLOCK_FENCED_CODE:
  150. cr(html);
  151. strbuf_puts(html, "<pre><code");
  152. if (b->tag == BLOCK_FENCED_CODE) {
  153. strbuf *info = &b->as.code.info;
  154. if (strbuf_len(info) > 0) {
  155. int first_tag = strbuf_strchr(info, ' ', 0);
  156. if (first_tag < 0)
  157. first_tag = strbuf_len(info);
  158. strbuf_puts(html, " class=\"language-");
  159. escape_html(html, info->ptr, first_tag);
  160. strbuf_putc(html, '"');
  161. }
  162. }
  163. strbuf_putc(html, '>');
  164. escape_html(html, b->string_content.ptr, b->string_content.size);
  165. strbuf_puts(html, "</code></pre>\n");
  166. break;
  167. case BLOCK_HTML:
  168. strbuf_put(html, b->string_content.ptr, b->string_content.size);
  169. break;
  170. case BLOCK_HRULE:
  171. strbuf_puts(html, "<hr />\n");
  172. break;
  173. case BLOCK_REFERENCE_DEF:
  174. break;
  175. default:
  176. assert(false);
  177. }
  178. b = b->next;
  179. }
  180. }
  181. void stmd_render_html(strbuf *html, node_block *root)
  182. {
  183. blocks_to_html(html, root, false);
  184. }