aboutsummaryrefslogtreecommitdiff
path: root/src/html/html.c
blob: 2a65a63ef1462153aaf03f06db1a3038822c2faa (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 block and inline lists to HTML strings.
  10. static void escape_html(gh_buf *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(gh_buf *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(gh_buf *html)
  23. {
  24. if (html->size && html->ptr[html->size - 1] != '\n')
  25. gh_buf_putc(html, '\n');
  26. }
  27. // Convert a block list to HTML. Returns 0 on success, and sets result.
  28. void blocks_to_html(gh_buf *html, block *b, bool tight)
  29. {
  30. struct ListData *data;
  31. while(b != NULL) {
  32. switch(b->tag) {
  33. case document:
  34. blocks_to_html(html, b->children, false);
  35. break;
  36. case paragraph:
  37. if (tight) {
  38. inlines_to_html(html, b->inline_content);
  39. } else {
  40. cr(html);
  41. gh_buf_puts(html, "<p>");
  42. inlines_to_html(html, b->inline_content);
  43. gh_buf_puts(html, "</p>");
  44. cr(html);
  45. }
  46. break;
  47. case block_quote:
  48. cr(html);
  49. gh_buf_puts(html, "<blockquote>");
  50. blocks_to_html(html, b->children, false);
  51. gh_buf_puts(html, "</blockquote>");
  52. cr(html);
  53. break;
  54. case list_item:
  55. cr(html);
  56. gh_buf_puts(html, "<li>");
  57. blocks_to_html(html, b->children, tight);
  58. gh_buf_trim(html); /* TODO: rtrim */
  59. gh_buf_puts(html, "</li>");
  60. cr(html);
  61. break;
  62. case list:
  63. // make sure a list starts at the beginning of the line:
  64. cr(html);
  65. data = &(b->attributes.list_data);
  66. if (data->start > 1) {
  67. gh_buf_printf(html, "<%s start=\"%d\">\n",
  68. data->list_type == bullet ? "ul" : "ol",
  69. data->start);
  70. } else {
  71. gh_buf_puts(html, data->list_type == bullet ? "<ul>\n" : "<ol>\n");
  72. }
  73. blocks_to_html(html, b->children, data->tight);
  74. gh_buf_puts(html, data->list_type == bullet ? "</ul>" : "</ol>");
  75. cr(html);
  76. break;
  77. case atx_header:
  78. case setext_header:
  79. cr(html);
  80. gh_buf_printf(html, "<h%d>", b->attributes.header_level);
  81. inlines_to_html(html, b->inline_content);
  82. gh_buf_printf(html, "</h%d>", b->attributes.header_level);
  83. cr(html);
  84. break;
  85. case indented_code:
  86. case fenced_code:
  87. cr(html);
  88. gh_buf_puts(html, "<pre");
  89. if (b->tag == fenced_code) {
  90. gh_buf *info = &b->attributes.fenced_code_data.info;
  91. if (gh_buf_len(info) > 0) {
  92. int first_tag = gh_buf_strchr(info, ' ', 0);
  93. if (first_tag < 0)
  94. first_tag = gh_buf_len(info);
  95. gh_buf_puts(html, " class=\"");
  96. escape_html(html, info->ptr, first_tag);
  97. gh_buf_putc(html, '"');
  98. }
  99. }
  100. gh_buf_puts(html, "><code>");
  101. escape_html(html, b->string_content.ptr, b->string_content.size);
  102. gh_buf_puts(html, "</code></pre>");
  103. cr(html);
  104. break;
  105. case html_block:
  106. gh_buf_put(html, b->string_content.ptr, b->string_content.size);
  107. break;
  108. case hrule:
  109. gh_buf_puts(html, "<hr />");
  110. cr(html);
  111. break;
  112. case reference_def:
  113. break;
  114. default:
  115. assert(false);
  116. }
  117. b = b->next;
  118. }
  119. }
  120. // Convert an inline list to HTML. Returns 0 on success, and sets result.
  121. void inlines_to_html(gh_buf *html, inl* ils)
  122. {
  123. gh_buf scrap = GH_BUF_INIT;
  124. while(ils != NULL) {
  125. switch(ils->tag) {
  126. case INL_STRING:
  127. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  128. break;
  129. case INL_LINEBREAK:
  130. gh_buf_puts(html, "<br />\n");
  131. break;
  132. case INL_SOFTBREAK:
  133. gh_buf_putc(html, '\n');
  134. break;
  135. case INL_CODE:
  136. gh_buf_puts(html, "<code>");
  137. escape_html(html, ils->content.literal.data, ils->content.literal.len);
  138. gh_buf_puts(html, "</code>");
  139. break;
  140. case INL_RAW_HTML:
  141. case INL_ENTITY:
  142. gh_buf_put(html,
  143. ils->content.literal.data,
  144. ils->content.literal.len);
  145. break;
  146. case INL_LINK:
  147. gh_buf_puts(html, "<a href=\"");
  148. escape_href(html, ils->content.linkable.url, -1);
  149. if (ils->content.linkable.title) {
  150. gh_buf_puts(html, "\" title=\"");
  151. escape_html(html, ils->content.linkable.title, -1);
  152. }
  153. gh_buf_puts(html, "\">");
  154. inlines_to_html(html, ils->content.inlines);
  155. gh_buf_puts(html, "</a>");
  156. break;
  157. case INL_IMAGE:
  158. gh_buf_puts(html, "<img src=\"");
  159. escape_href(html, ils->content.linkable.url, -1);
  160. inlines_to_html(&scrap, ils->content.inlines);
  161. if (scrap.size) {
  162. gh_buf_puts(html, "\" alt=\"");
  163. escape_html(html, scrap.ptr, scrap.size);
  164. }
  165. gh_buf_clear(&scrap);
  166. if (ils->content.linkable.title) {
  167. gh_buf_puts(html, "\" title=\"");
  168. escape_html(html, ils->content.linkable.title, -1);
  169. }
  170. gh_buf_puts(html, "\"/>");
  171. break;
  172. case INL_STRONG:
  173. gh_buf_puts(html, "<strong>");
  174. inlines_to_html(html, ils->content.inlines);
  175. gh_buf_puts(html, "</strong>");
  176. break;
  177. case INL_EMPH:
  178. gh_buf_puts(html, "<em>");
  179. inlines_to_html(html, ils->content.inlines);
  180. gh_buf_puts(html, "</em>");
  181. break;
  182. }
  183. ils = ils->next;
  184. }
  185. }