| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <assert.h> |
| |
| #include "config.h" |
| #include "cmark.h" |
| #include "node.h" |
| #include "buffer.h" |
| #include "houdini.h" |
| |
| // Functions to convert cmark_nodes to XML strings. |
| |
| static void escape_xml(cmark_strbuf *dest, const unsigned char *source, |
| bufsize_t length) { |
| houdini_escape_html0(dest, source, length, 0); |
| } |
| |
| struct render_state { |
| cmark_strbuf *xml; |
| int indent; |
| }; |
| |
| static inline void indent(struct render_state *state) { |
| int i; |
| for (i = 0; i < state->indent; i++) { |
| cmark_strbuf_putc(state->xml, ' '); |
| } |
| } |
| |
| static int S_render_node(cmark_node *node, cmark_event_type ev_type, |
| struct render_state *state, int options) { |
| cmark_strbuf *xml = state->xml; |
| bool literal = false; |
| cmark_delim_type delim; |
| bool entering = (ev_type == CMARK_EVENT_ENTER); |
| char buffer[100]; |
| |
| if (entering) { |
| indent(state); |
| cmark_strbuf_putc(xml, '<'); |
| cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); |
| |
| if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) { |
| sprintf(buffer, " sourcepos=\"%d:%d-%d:%d\"", node->start_line, |
| node->start_column, node->end_line, node->end_column); |
| cmark_strbuf_puts(xml, buffer); |
| } |
| |
| literal = false; |
| |
| switch (node->type) { |
| case CMARK_NODE_TEXT: |
| case CMARK_NODE_CODE: |
| case CMARK_NODE_HTML: |
| case CMARK_NODE_INLINE_HTML: |
| cmark_strbuf_puts(xml, ">"); |
| escape_xml(xml, node->as.literal.data, node->as.literal.len); |
| cmark_strbuf_puts(xml, "</"); |
| cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); |
| literal = true; |
| break; |
| case CMARK_NODE_LIST: |
| switch (cmark_node_get_list_type(node)) { |
| case CMARK_ORDERED_LIST: |
| cmark_strbuf_puts(xml, " type=\"ordered\""); |
| sprintf(buffer, " start=\"%d\"", cmark_node_get_list_start(node)); |
| cmark_strbuf_puts(xml, buffer); |
| delim = cmark_node_get_list_delim(node); |
| if (delim == CMARK_PAREN_DELIM) { |
| cmark_strbuf_puts(xml, " delim=\"paren\""); |
| } else if (delim == CMARK_PERIOD_DELIM) { |
| cmark_strbuf_puts(xml, " delim=\"period\""); |
| } |
| break; |
| case CMARK_BULLET_LIST: |
| cmark_strbuf_puts(xml, " type=\"bullet\""); |
| break; |
| default: |
| break; |
| } |
| sprintf(buffer, " tight=\"%s\"", |
| (cmark_node_get_list_tight(node) ? "true" : "false")); |
| cmark_strbuf_puts(xml, buffer); |
| break; |
| case CMARK_NODE_HEADER: |
| sprintf(buffer, " level=\"%d\"", node->as.header.level); |
| cmark_strbuf_puts(xml, buffer); |
| break; |
| case CMARK_NODE_CODE_BLOCK: |
| if (node->as.code.info.len > 0) { |
| cmark_strbuf_puts(xml, " info=\""); |
| escape_xml(xml, node->as.code.info.data, node->as.code.info.len); |
| cmark_strbuf_putc(xml, '"'); |
| } |
| cmark_strbuf_puts(xml, ">"); |
| escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len); |
| cmark_strbuf_puts(xml, "</"); |
| cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); |
| literal = true; |
| break; |
| case CMARK_NODE_LINK: |
| case CMARK_NODE_IMAGE: |
| cmark_strbuf_puts(xml, " destination=\""); |
| escape_xml(xml, node->as.link.url.data, node->as.link.url.len); |
| cmark_strbuf_putc(xml, '"'); |
| cmark_strbuf_puts(xml, " title=\""); |
| escape_xml(xml, node->as.link.title.data, node->as.link.title.len); |
| cmark_strbuf_putc(xml, '"'); |
| break; |
| default: |
| break; |
| } |
| if (node->first_child) { |
| state->indent += 2; |
| } else if (!literal) { |
| cmark_strbuf_puts(xml, " /"); |
| } |
| cmark_strbuf_puts(xml, ">\n"); |
| |
| } else if (node->first_child) { |
| state->indent -= 2; |
| indent(state); |
| cmark_strbuf_puts(xml, "</"); |
| cmark_strbuf_puts(xml, cmark_node_get_type_string(node)); |
| cmark_strbuf_puts(xml, ">\n"); |
| } |
| |
| return 1; |
| } |
| |
| char *cmark_render_xml(cmark_node *root, int options) { |
| char *result; |
| cmark_strbuf xml = GH_BUF_INIT; |
| cmark_event_type ev_type; |
| cmark_node *cur; |
| struct render_state state = {&xml, 0}; |
| |
| cmark_iter *iter = cmark_iter_new(root); |
| |
| cmark_strbuf_puts(state.xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); |
| cmark_strbuf_puts(state.xml, |
| "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n"); |
| 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(&xml); |
| |
| cmark_iter_free(iter); |
| return result; |
| } |