1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <assert.h>
5 
6 #include "config.h"
7 #include "cmark.h"
8 #include "node.h"
9 #include "buffer.h"
10 #include "houdini.h"
11 
12 #define BUFFER_SIZE 100
13 
14 // Functions to convert cmark_nodes to XML strings.
15 
escape_xml(cmark_strbuf * dest,const unsigned char * source,bufsize_t length)16 static void escape_xml(cmark_strbuf *dest, const unsigned char *source,
17                        bufsize_t length) {
18   houdini_escape_html0(dest, source, length, 0);
19 }
20 
21 struct render_state {
22   cmark_strbuf *xml;
23   int indent;
24 };
25 
indent(struct render_state * state)26 static CMARK_INLINE void indent(struct render_state *state) {
27   int i;
28   for (i = 0; i < state->indent; i++) {
29     cmark_strbuf_putc(state->xml, ' ');
30   }
31 }
32 
S_render_node(cmark_node * node,cmark_event_type ev_type,struct render_state * state,int options)33 static int S_render_node(cmark_node *node, cmark_event_type ev_type,
34                          struct render_state *state, int options) {
35   cmark_strbuf *xml = state->xml;
36   bool literal = false;
37   cmark_delim_type delim;
38   bool entering = (ev_type == CMARK_EVENT_ENTER);
39   char buffer[BUFFER_SIZE];
40 
41   if (entering) {
42     indent(state);
43     cmark_strbuf_putc(xml, '<');
44     cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
45 
46     if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) {
47       snprintf(buffer, BUFFER_SIZE, " sourcepos=\"%d:%d-%d:%d\"",
48                node->start_line, node->start_column, node->end_line,
49                node->end_column);
50       cmark_strbuf_puts(xml, buffer);
51     }
52 
53     literal = false;
54 
55     switch (node->type) {
56     case CMARK_NODE_DOCUMENT:
57       cmark_strbuf_puts(xml, " xmlns=\"http://commonmark.org/xml/1.0\"");
58       break;
59     case CMARK_NODE_TEXT:
60     case CMARK_NODE_CODE:
61     case CMARK_NODE_HTML_BLOCK:
62     case CMARK_NODE_HTML_INLINE:
63       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
64       escape_xml(xml, node->data, node->len);
65       cmark_strbuf_puts(xml, "</");
66       cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
67       literal = true;
68       break;
69     case CMARK_NODE_LIST:
70       switch (cmark_node_get_list_type(node)) {
71       case CMARK_ORDERED_LIST:
72         cmark_strbuf_puts(xml, " type=\"ordered\"");
73         snprintf(buffer, BUFFER_SIZE, " start=\"%d\"",
74                  cmark_node_get_list_start(node));
75         cmark_strbuf_puts(xml, buffer);
76         delim = cmark_node_get_list_delim(node);
77         if (delim == CMARK_PAREN_DELIM) {
78           cmark_strbuf_puts(xml, " delim=\"paren\"");
79         } else if (delim == CMARK_PERIOD_DELIM) {
80           cmark_strbuf_puts(xml, " delim=\"period\"");
81         }
82         break;
83       case CMARK_BULLET_LIST:
84         cmark_strbuf_puts(xml, " type=\"bullet\"");
85         break;
86       default:
87         break;
88       }
89       snprintf(buffer, BUFFER_SIZE, " tight=\"%s\"",
90                (cmark_node_get_list_tight(node) ? "true" : "false"));
91       cmark_strbuf_puts(xml, buffer);
92       break;
93     case CMARK_NODE_HEADING:
94       snprintf(buffer, BUFFER_SIZE, " level=\"%d\"", node->as.heading.level);
95       cmark_strbuf_puts(xml, buffer);
96       break;
97     case CMARK_NODE_CODE_BLOCK:
98       if (node->as.code.info) {
99         cmark_strbuf_puts(xml, " info=\"");
100         escape_xml(xml, node->as.code.info, strlen((char *)node->as.code.info));
101         cmark_strbuf_putc(xml, '"');
102       }
103       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
104       escape_xml(xml, node->data, node->len);
105       cmark_strbuf_puts(xml, "</");
106       cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
107       literal = true;
108       break;
109     case CMARK_NODE_CUSTOM_BLOCK:
110     case CMARK_NODE_CUSTOM_INLINE:
111       cmark_strbuf_puts(xml, " on_enter=\"");
112       escape_xml(xml, node->as.custom.on_enter,
113                  strlen((char *)node->as.custom.on_enter));
114       cmark_strbuf_putc(xml, '"');
115       cmark_strbuf_puts(xml, " on_exit=\"");
116       escape_xml(xml, node->as.custom.on_exit,
117                  strlen((char *)node->as.custom.on_exit));
118       cmark_strbuf_putc(xml, '"');
119       break;
120     case CMARK_NODE_LINK:
121     case CMARK_NODE_IMAGE:
122       cmark_strbuf_puts(xml, " destination=\"");
123       escape_xml(xml, node->as.link.url, strlen((char *)node->as.link.url));
124       cmark_strbuf_putc(xml, '"');
125       if (node->as.link.title) {
126         cmark_strbuf_puts(xml, " title=\"");
127         escape_xml(xml, node->as.link.title,
128                    strlen((char *)node->as.link.title));
129         cmark_strbuf_putc(xml, '"');
130       }
131       break;
132     default:
133       break;
134     }
135     if (node->first_child) {
136       state->indent += 2;
137     } else if (!literal) {
138       cmark_strbuf_puts(xml, " /");
139     }
140     cmark_strbuf_puts(xml, ">\n");
141 
142   } else if (node->first_child) {
143     state->indent -= 2;
144     indent(state);
145     cmark_strbuf_puts(xml, "</");
146     cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
147     cmark_strbuf_puts(xml, ">\n");
148   }
149 
150   return 1;
151 }
152 
cmark_render_xml(cmark_node * root,int options)153 char *cmark_render_xml(cmark_node *root, int options) {
154   char *result;
155   cmark_strbuf xml = CMARK_BUF_INIT(root->mem);
156   cmark_event_type ev_type;
157   cmark_node *cur;
158   struct render_state state = {&xml, 0};
159 
160   cmark_iter *iter = cmark_iter_new(root);
161 
162   cmark_strbuf_puts(state.xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
163   cmark_strbuf_puts(state.xml,
164                     "<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n");
165   while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
166     cur = cmark_iter_get_node(iter);
167     S_render_node(cur, ev_type, &state, options);
168   }
169   result = (char *)cmark_strbuf_detach(&xml);
170 
171   cmark_iter_free(iter);
172   return result;
173 }
174