aboutsummaryrefslogtreecommitdiff
path: root/src/xml.c
blob: f630aba16462fbbf065bbca572dbf3f7385176d0 (plain)
  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. // Functions to convert cmark_nodes to XML strings.
  11. static void escape_xml(cmark_strbuf *dest, const unsigned char *source, int length)
  12. {
  13. if (source != NULL) {
  14. if (length < 0)
  15. length = strlen((char *)source);
  16. houdini_escape_html0(dest, source, (size_t)length, 0);
  17. }
  18. }
  19. struct render_state {
  20. cmark_strbuf* xml;
  21. int indent;
  22. };
  23. static inline void indent(struct render_state *state)
  24. {
  25. int i;
  26. for (i = 0; i < state->indent; i++) {
  27. cmark_strbuf_putc(state->xml, ' ');
  28. }
  29. }
  30. static int
  31. S_render_node(cmark_node *node, cmark_event_type ev_type,
  32. struct render_state *state, long options)
  33. {
  34. cmark_strbuf *xml = state->xml;
  35. bool literal = false;
  36. cmark_delim_type delim;
  37. bool entering = (ev_type == CMARK_EVENT_ENTER);
  38. if (entering) {
  39. indent(state);
  40. cmark_strbuf_printf(xml, "<%s",
  41. cmark_node_get_type_string(node));
  42. if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) {
  43. cmark_strbuf_printf(xml, " sourcepos=\"%d:%d-%d:%d\"",
  44. node->start_line,
  45. node->start_column,
  46. node->end_line,
  47. node->end_column);
  48. }
  49. literal = false;
  50. switch (node->type) {
  51. case CMARK_NODE_TEXT:
  52. case CMARK_NODE_CODE:
  53. case CMARK_NODE_HTML:
  54. case CMARK_NODE_INLINE_HTML:
  55. cmark_strbuf_puts(xml, ">");
  56. escape_xml(xml, node->as.literal.data,
  57. node->as.literal.len);
  58. cmark_strbuf_puts(xml, "</");
  59. cmark_strbuf_puts(xml,
  60. cmark_node_get_type_string(node));
  61. literal = true;
  62. break;
  63. case CMARK_NODE_LIST:
  64. switch (cmark_node_get_list_type(node)) {
  65. case CMARK_ORDERED_LIST:
  66. cmark_strbuf_puts(xml, " type=\"ordered\"");
  67. cmark_strbuf_printf(xml, " start=\"%d\"",
  68. cmark_node_get_list_start(node));
  69. delim = cmark_node_get_list_delim(node);
  70. if (delim == CMARK_PAREN_DELIM) {
  71. cmark_strbuf_puts(xml,
  72. " delim=\"paren\"");
  73. } else if (delim == CMARK_PERIOD_DELIM) {
  74. cmark_strbuf_puts(xml,
  75. " delim=\"period\"");
  76. }
  77. break;
  78. case CMARK_BULLET_LIST:
  79. cmark_strbuf_puts(xml, " type=\"bullet\"");
  80. break;
  81. default:
  82. break;
  83. }
  84. cmark_strbuf_printf(xml, " tight=\"%s\"",
  85. (cmark_node_get_list_tight(node) ?
  86. "true" : "false"));
  87. break;
  88. case CMARK_NODE_HEADER:
  89. cmark_strbuf_printf(xml, " level=\"%d\"",
  90. node->as.header.level);
  91. break;
  92. case CMARK_NODE_CODE_BLOCK:
  93. if (node->as.code.info.len > 0) {
  94. cmark_strbuf_puts(xml, " info=\"");
  95. escape_xml(xml, node->as.code.info.data,
  96. node->as.code.info.len);
  97. cmark_strbuf_putc(xml, '"');
  98. }
  99. cmark_strbuf_puts(xml, ">");
  100. escape_xml(xml, node->as.code.literal.data,
  101. node->as.code.literal.len);
  102. cmark_strbuf_puts(xml, "</");
  103. cmark_strbuf_puts(xml,
  104. cmark_node_get_type_string(node));
  105. literal = true;
  106. break;
  107. case CMARK_NODE_LINK:
  108. case CMARK_NODE_IMAGE:
  109. cmark_strbuf_puts(xml, " destination=\"");
  110. escape_xml(xml, node->as.link.url, -1);
  111. cmark_strbuf_putc(xml, '"');
  112. cmark_strbuf_puts(xml, " title=\"");
  113. escape_xml(xml, node->as.link.title, -1);
  114. cmark_strbuf_putc(xml, '"');
  115. break;
  116. default:
  117. break;
  118. }
  119. if (node->first_child) {
  120. state->indent += 2;
  121. } else if (!literal) {
  122. cmark_strbuf_puts(xml, " /");
  123. }
  124. cmark_strbuf_puts(xml, ">\n");
  125. } else if (node->first_child) {
  126. state->indent -= 2;
  127. indent(state);
  128. cmark_strbuf_printf(xml, "</%s>\n",
  129. cmark_node_get_type_string(node));
  130. }
  131. return 1;
  132. }
  133. char *cmark_render_xml(cmark_node *root, long options)
  134. {
  135. char *result;
  136. cmark_strbuf xml = GH_BUF_INIT;
  137. cmark_event_type ev_type;
  138. cmark_node *cur;
  139. struct render_state state = { &xml, 0 };
  140. if (options & CMARK_OPT_NORMALIZE) {
  141. cmark_consolidate_text_nodes(root);
  142. }
  143. cmark_iter *iter = cmark_iter_new(root);
  144. cmark_strbuf_puts(state.xml,
  145. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  146. cmark_strbuf_puts(state.xml,
  147. "<!DOCTYPE CommonMark SYSTEM \"CommonMark.dtd\">\n");
  148. while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
  149. cur = cmark_iter_get_node(iter);
  150. S_render_node(cur, ev_type, &state, options);
  151. }
  152. result = (char *)cmark_strbuf_detach(&xml);
  153. cmark_iter_free(iter);
  154. cmark_strbuf_free(&xml);
  155. return result;
  156. }