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