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