aboutsummaryrefslogtreecommitdiff
path: root/js/lib/xml.js
blob: c90704e1e54058c13016abd8ee0239f1d321dfcf (plain)
  1. "use strict";
  2. // Helper function to produce an XML tag.
  3. var tag = function(name, attrs, selfclosing) {
  4. var result = '<' + name;
  5. if (attrs && attrs.length > 0) {
  6. var i = 0;
  7. var attrib;
  8. while ((attrib = attrs[i]) !== undefined) {
  9. result = result.concat(' ', attrib[0], '="', attrib[1], '"');
  10. i++;
  11. }
  12. }
  13. if (selfclosing) {
  14. result += ' /';
  15. }
  16. result += '>';
  17. return result;
  18. };
  19. var reXMLTag = /\<[^>]*\>/;
  20. var toTagName = function(s) {
  21. return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
  22. };
  23. var renderNodes = function(block) {
  24. var attrs;
  25. var info_words;
  26. var tagname;
  27. var walker = block.walker();
  28. var event, node, entering;
  29. var buffer = "";
  30. var lastOut = "\n";
  31. var disableTags = 0;
  32. var indentLevel = 0;
  33. var indent = ' ';
  34. var grandparent;
  35. var unescapedContents;
  36. var container;
  37. var selfClosing;
  38. var out = function(s) {
  39. if (disableTags > 0) {
  40. buffer += s.replace(reXMLTag, '');
  41. } else {
  42. buffer += s;
  43. }
  44. lastOut = s;
  45. };
  46. var esc = this.escape;
  47. var cr = function() {
  48. if (lastOut !== '\n') {
  49. buffer += '\n';
  50. lastOut = '\n';
  51. for (var i = indentLevel; i--;) {
  52. buffer += indent;
  53. }
  54. }
  55. };
  56. var options = this.options;
  57. if (options.time) { console.time("rendering"); }
  58. buffer += '<?xml version="1.0" encoding="UTF-8"?>\n';
  59. buffer += '<!DOCTYPE CommonMark SYSTEM "CommonMark.dtd">\n';
  60. while ((event = walker.next())) {
  61. entering = event.entering;
  62. node = event.node;
  63. if (node.t === 'ReferenceDef') {
  64. continue;
  65. }
  66. container = node.isContainer();
  67. selfClosing = node.t === 'HorizontalRule' || node.t === 'Hardbreak' ||
  68. node.t === 'Softbreak' || node.t === 'Image';
  69. unescapedContents = node.t === 'Html' || node.t === 'HtmlInline';
  70. tagname = toTagName(node.t);
  71. if (entering) {
  72. attrs = [];
  73. if (node.list_data) {
  74. var data = node.list_data;
  75. if (data.type !== undefined) {
  76. attrs.push(['type', data.type.toLowerCase()]);
  77. }
  78. if (data.start !== undefined) {
  79. attrs.push(['start', String(data.start)]);
  80. }
  81. if (data.tight !== undefined) {
  82. attrs.push(['tight', (data.tight ? 'true' : 'false')]);
  83. }
  84. if (data.delimiter !== undefined) {
  85. var delimword = '';
  86. if (data.delimiter === '.') {
  87. delimword = 'period';
  88. } else {
  89. delimword = 'paren';
  90. }
  91. attrs.push(['delimiter', delimword]);
  92. }
  93. }
  94. if (node.info !== undefined) {
  95. attrs.push(['info', node.info]);
  96. }
  97. if (node.level !== undefined) {
  98. attrs.push(['level', String(node.level)]);
  99. }
  100. if (node.destination !== undefined) {
  101. attrs.push(['destination', node.destination]);
  102. }
  103. if (node.title !== undefined) {
  104. attrs.push(['title', node.title]);
  105. }
  106. if (options.sourcepos) {
  107. var pos = node.sourcepos;
  108. if (pos !== undefined) {
  109. attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
  110. String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
  111. String(pos[1][1])]);
  112. }
  113. }
  114. cr();
  115. out(tag(tagname, attrs, selfClosing));
  116. if (container) {
  117. indentLevel += 1;
  118. } else if (!container && !selfClosing) {
  119. if (node.literal) {
  120. out(unescapedContents ? node.literal : esc(node.literal));
  121. }
  122. out(tag('/' + tagname));
  123. }
  124. } else {
  125. indentLevel -= 1;
  126. cr();
  127. out(tag('/' + tagname));
  128. }
  129. }
  130. if (options.time) { console.timeEnd("rendering"); }
  131. buffer += '\n';
  132. return buffer;
  133. };
  134. var replaceUnsafeChar = function(s) {
  135. switch (s) {
  136. case '&':
  137. return '&amp;';
  138. case '<':
  139. return '&lt;';
  140. case '>':
  141. return '&gt;';
  142. case '"':
  143. return '&quot;';
  144. default:
  145. return s;
  146. }
  147. };
  148. var reNeedsEscaping = /[&<>"]/;
  149. // The XmlRenderer object.
  150. function XmlRenderer(options){
  151. return {
  152. // default options:
  153. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  154. // set to "<br />" to make them hard breaks
  155. // set to " " if you want to ignore line wrapping in source
  156. escape: function(s, preserve_entities) {
  157. if (reNeedsEscaping.test(s)) {
  158. if (preserve_entities) {
  159. return s.replace(/[&](?:[#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)|[&<>"]/gi, replaceUnsafeChar);
  160. } else {
  161. return s.replace(/[&<>"]/g, replaceUnsafeChar);
  162. }
  163. } else {
  164. return s;
  165. }
  166. },
  167. options: options || {},
  168. render: renderNodes
  169. };
  170. }
  171. module.exports = XmlRenderer;