xml.c 5.3 KB

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