| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| #include "cmark_ctype.h" |
| #include "config.h" |
| #include "cmark.h" |
| #include "node.h" |
| #include "buffer.h" |
| #include "houdini.h" |
| #include "scanners.h" |
| |
| // Functions to convert cmark_nodes to HTML strings. |
| |
| static void escape_html(cmark_strbuf *dest, const unsigned char *source, |
| bufsize_t length) { |
| houdini_escape_html0(dest, source, length, 0); |
| } |
| |
| static inline void cr(cmark_strbuf *html) { |
| if (html->size && html->ptr[html->size - 1] != '\n') |
| cmark_strbuf_putc(html, '\n'); |
| } |
| |
| struct render_state { |
| cmark_strbuf *html; |
| cmark_node *plain; |
| }; |
| |
| static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html, |
| int options) { |
| char buffer[100]; |
| if (CMARK_OPT_SOURCEPOS & options) { |
| sprintf(buffer, " data-sourcepos=\"%d:%d-%d:%d\"", |
| cmark_node_get_start_line(node), cmark_node_get_start_column(node), |
| cmark_node_get_end_line(node), cmark_node_get_end_column(node)); |
| cmark_strbuf_puts(html, buffer); |
| } |
| } |
| |
| static int S_render_node(cmark_node *node, cmark_event_type ev_type, |
| struct render_state *state, int options) { |
| cmark_node *parent; |
| cmark_node *grandparent; |
| cmark_strbuf *html = state->html; |
| char start_header[] = "<h0"; |
| char end_header[] = "</h0"; |
| bool tight; |
| char buffer[100]; |
| |
| bool entering = (ev_type == CMARK_EVENT_ENTER); |
| |
| if (state->plain == node) { // back at original node |
| state->plain = NULL; |
| } |
| |
| if (state->plain != NULL) { |
| switch (node->type) { |
| case CMARK_NODE_TEXT: |
| case CMARK_NODE_CODE: |
| case CMARK_NODE_INLINE_HTML: |
| escape_html(html, node->as.literal.data, node->as.literal.len); |
| break; |
| |
| case CMARK_NODE_LINEBREAK: |
| case CMARK_NODE_SOFTBREAK: |
| cmark_strbuf_putc(html, ' '); |
| break; |
| |
| default: |
| break; |
| } |
| return 1; |
| } |
| |
| switch (node->type) { |
| case CMARK_NODE_DOCUMENT: |
| break; |
| |
| case CMARK_NODE_BLOCK_QUOTE: |
| if (entering) { |
| cr(html); |
| cmark_strbuf_puts(html, "<blockquote"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, ">\n"); |
| } else { |
| cr(html); |
| cmark_strbuf_puts(html, "</blockquote>\n"); |
| } |
| break; |
| |
| case CMARK_NODE_LIST: { |
| cmark_list_type list_type = node->as.list.list_type; |
| int start = node->as.list.start; |
| |
| if (entering) { |
| cr(html); |
| if (list_type == CMARK_BULLET_LIST) { |
| cmark_strbuf_puts(html, "<ul"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, ">\n"); |
| } else if (start == 1) { |
| cmark_strbuf_puts(html, "<ol"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, ">\n"); |
| } else { |
| sprintf(buffer, "<ol start=\"%d\"", start); |
| cmark_strbuf_puts(html, buffer); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, ">\n"); |
| } |
| } else { |
| cmark_strbuf_puts(html, |
| list_type == CMARK_BULLET_LIST ? "</ul>\n" : "</ol>\n"); |
| } |
| break; |
| } |
| |
| case CMARK_NODE_ITEM: |
| if (entering) { |
| cr(html); |
| cmark_strbuf_puts(html, "<li"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_putc(html, '>'); |
| } else { |
| cmark_strbuf_puts(html, "</li>\n"); |
| } |
| break; |
| |
| case CMARK_NODE_HEADER: |
| if (entering) { |
| cr(html); |
| start_header[2] = (char)('0' + node->as.header.level); |
| cmark_strbuf_puts(html, start_header); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_putc(html, '>'); |
| } else { |
| end_header[3] = (char)('0' + node->as.header.level); |
| cmark_strbuf_puts(html, end_header); |
| cmark_strbuf_puts(html, ">\n"); |
| } |
| break; |
| |
| case CMARK_NODE_CODE_BLOCK: |
| cr(html); |
| |
| if (!node->as.code.fenced || node->as.code.info.len == 0) { |
| cmark_strbuf_puts(html, "<pre"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, "><code>"); |
| } else { |
| bufsize_t first_tag = 0; |
| while (first_tag < node->as.code.info.len && |
| !cmark_isspace(node->as.code.info.data[first_tag])) { |
| first_tag += 1; |
| } |
| |
| cmark_strbuf_puts(html, "<pre"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, "><code class=\"language-"); |
| escape_html(html, node->as.code.info.data, first_tag); |
| cmark_strbuf_puts(html, "\">"); |
| } |
| |
| escape_html(html, node->as.code.literal.data, node->as.code.literal.len); |
| cmark_strbuf_puts(html, "</code></pre>\n"); |
| break; |
| |
| case CMARK_NODE_HTML: |
| cr(html); |
| if (options & CMARK_OPT_SAFE) { |
| cmark_strbuf_puts(html, "<!-- raw HTML omitted -->"); |
| } else { |
| cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); |
| } |
| cr(html); |
| break; |
| |
| case CMARK_NODE_HRULE: |
| cr(html); |
| cmark_strbuf_puts(html, "<hr"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_puts(html, " />\n"); |
| break; |
| |
| case CMARK_NODE_PARAGRAPH: |
| parent = cmark_node_parent(node); |
| grandparent = cmark_node_parent(parent); |
| if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) { |
| tight = grandparent->as.list.tight; |
| } else { |
| tight = false; |
| } |
| if (!tight) { |
| if (entering) { |
| cr(html); |
| cmark_strbuf_puts(html, "<p"); |
| S_render_sourcepos(node, html, options); |
| cmark_strbuf_putc(html, '>'); |
| } else { |
| cmark_strbuf_puts(html, "</p>\n"); |
| } |
| } |
| break; |
| |
| case CMARK_NODE_TEXT: |
| escape_html(html, node->as.literal.data, node->as.literal.len); |
| break; |
| |
| case CMARK_NODE_LINEBREAK: |
| cmark_strbuf_puts(html, "<br />\n"); |
| break; |
| |
| case CMARK_NODE_SOFTBREAK: |
| if (options & CMARK_OPT_HARDBREAKS) { |
| cmark_strbuf_puts(html, "<br />\n"); |
| } else { |
| cmark_strbuf_putc(html, '\n'); |
| } |
| break; |
| |
| case CMARK_NODE_CODE: |
| cmark_strbuf_puts(html, "<code>"); |
| escape_html(html, node->as.literal.data, node->as.literal.len); |
| cmark_strbuf_puts(html, "</code>"); |
| break; |
| |
| case CMARK_NODE_INLINE_HTML: |
| if (options & CMARK_OPT_SAFE) { |
| cmark_strbuf_puts(html, "<!-- raw HTML omitted -->"); |
| } else { |
| cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); |
| } |
| break; |
| |
| case CMARK_NODE_STRONG: |
| if (entering) { |
| cmark_strbuf_puts(html, "<strong>"); |
| } else { |
| cmark_strbuf_puts(html, "</strong>"); |
| } |
| break; |
| |
| case CMARK_NODE_EMPH: |
| if (entering) { |
| cmark_strbuf_puts(html, "<em>"); |
| } else { |
| cmark_strbuf_puts(html, "</em>"); |
| } |
| break; |
| |
| case CMARK_NODE_LINK: |
| if (entering) { |
| cmark_strbuf_puts(html, "<a href=\""); |
| if (!((options & CMARK_OPT_SAFE) && |
| scan_dangerous_url(&node->as.link.url, 0))) { |
| houdini_escape_href(html, node->as.link.url.data, |
| node->as.link.url.len); |
| } |
| if (node->as.link.title.len) { |
| cmark_strbuf_puts(html, "\" title=\""); |
| escape_html(html, node->as.link.title.data, node->as.link.title.len); |
| } |
| cmark_strbuf_puts(html, "\">"); |
| } else { |
| cmark_strbuf_puts(html, "</a>"); |
| } |
| break; |
| |
| case CMARK_NODE_IMAGE: |
| if (entering) { |
| cmark_strbuf_puts(html, "<img src=\""); |
| if (!((options & CMARK_OPT_SAFE) && |
| scan_dangerous_url(&node->as.link.url, 0))) { |
| houdini_escape_href(html, node->as.link.url.data, |
| node->as.link.url.len); |
| } |
| cmark_strbuf_puts(html, "\" alt=\""); |
| state->plain = node; |
| } else { |
| if (node->as.link.title.len) { |
| cmark_strbuf_puts(html, "\" title=\""); |
| escape_html(html, node->as.link.title.data, node->as.link.title.len); |
| } |
| |
| cmark_strbuf_puts(html, "\" />"); |
| } |
| break; |
| |
| default: |
| assert(false); |
| break; |
| } |
| |
| // cmark_strbuf_putc(html, 'x'); |
| return 1; |
| } |
| |
| char *cmark_render_html(cmark_node *root, int options) { |
| char *result; |
| cmark_strbuf html = GH_BUF_INIT; |
| cmark_event_type ev_type; |
| cmark_node *cur; |
| struct render_state state = {&html, NULL}; |
| cmark_iter *iter = cmark_iter_new(root); |
| |
| while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { |
| cur = cmark_iter_get_node(iter); |
| S_render_node(cur, ev_type, &state, options); |
| } |
| result = (char *)cmark_strbuf_detach(&html); |
| |
| cmark_iter_free(iter); |
| return result; |
| } |