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