aboutsummaryrefslogtreecommitdiff
path: root/js/lib/xml.js
blob: 7168ffa88aa629d66e00a60cc238f39ae5a7261a (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 renderNodes = function(block) {
  21. var attrs;
  22. var info_words;
  23. var tagname;
  24. var walker = block.walker();
  25. var event, node, entering;
  26. var buffer = "";
  27. var lastOut = "\n";
  28. var disableTags = 0;
  29. var indentLevel = 0;
  30. var indent = ' ';
  31. var grandparent;
  32. var out = function(s) {
  33. if (disableTags > 0) {
  34. buffer += s.replace(reXMLTag, '');
  35. } else {
  36. buffer += s;
  37. }
  38. lastOut = s;
  39. };
  40. var esc = this.escape;
  41. var cr = function() {
  42. if (lastOut !== '\n') {
  43. buffer += '\n';
  44. lastOut = '\n';
  45. for (var i = indentLevel; i--;) {
  46. buffer += indent;
  47. }
  48. }
  49. };
  50. var options = this.options;
  51. if (options.time) { console.time("rendering"); }
  52. while ((event = walker.next())) {
  53. entering = event.entering;
  54. node = event.node;
  55. attrs = [];
  56. if (options.sourcepos) {
  57. var pos = node.sourcepos;
  58. if (pos) {
  59. attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
  60. String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
  61. String(pos[1][1])]);
  62. }
  63. }
  64. switch (node.t) {
  65. case 'Text':
  66. out(esc(node.literal));
  67. break;
  68. case 'Softbreak':
  69. out(this.softbreak);
  70. break;
  71. case 'Hardbreak':
  72. out(tag('br', [], true));
  73. cr();
  74. break;
  75. case 'Emph':
  76. out(tag(entering ? 'em' : '/em'));
  77. break;
  78. case 'Strong':
  79. out(tag(entering ? 'strong' : '/strong'));
  80. break;
  81. case 'XML':
  82. out(node.literal);
  83. break;
  84. case 'Link':
  85. if (entering) {
  86. attrs.push(['href', esc(node.destination, true)]);
  87. if (node.title) {
  88. attrs.push(['title', esc(node.title, true)]);
  89. }
  90. out(tag('a', attrs));
  91. } else {
  92. out(tag('/a'));
  93. }
  94. break;
  95. case 'Image':
  96. if (entering) {
  97. if (disableTags === 0) {
  98. out('<img src="' + esc(node.destination, true) +
  99. '" alt="');
  100. }
  101. disableTags += 1;
  102. } else {
  103. disableTags -= 1;
  104. if (disableTags === 0) {
  105. if (node.title) {
  106. out('" title="' + esc(node.title, true));
  107. }
  108. out('" />');
  109. }
  110. }
  111. break;
  112. case 'Code':
  113. out(tag('code') + esc(node.literal) + tag('/code'));
  114. break;
  115. case 'Document':
  116. if (!entering) {
  117. cr();
  118. }
  119. break;
  120. case 'Paragraph':
  121. grandparent = node.parent.parent;
  122. if (grandparent !== null &&
  123. grandparent.t === 'List') {
  124. if (grandparent.list_data.tight) {
  125. break;
  126. }
  127. }
  128. if (entering) {
  129. cr();
  130. out(tag('p', attrs));
  131. } else {
  132. out(tag('/p'));
  133. }
  134. break;
  135. case 'BlockQuote':
  136. if (entering) {
  137. cr();
  138. out(tag('blockquote', attrs));
  139. indentLevel += 1;
  140. } else {
  141. indentLevel -= 1;
  142. cr();
  143. out(tag('/blockquote'));
  144. }
  145. break;
  146. case 'ListItem':
  147. if (entering) {
  148. cr();
  149. out(tag('li', attrs));
  150. } else {
  151. out(tag('/li'));
  152. }
  153. break;
  154. case 'List':
  155. tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol';
  156. if (entering) {
  157. if (node.list_data.start && node.list_data.start > 1) {
  158. attrs.push(['start', node.list_data.start.toString()]);
  159. }
  160. cr();
  161. out(tag(tagname, attrs));
  162. } else {
  163. cr();
  164. out(tag('/' + tagname));
  165. }
  166. break;
  167. case 'Header':
  168. tagname = 'h' + node.level;
  169. if (entering) {
  170. cr();
  171. out(tag(tagname, attrs));
  172. } else {
  173. out(tag('/' + tagname));
  174. }
  175. break;
  176. case 'CodeBlock':
  177. info_words = node.info ? node.info.split(/ +/) : [];
  178. if (info_words.length > 0 && info_words[0].length > 0) {
  179. attrs.push(['class', 'language-' + esc(info_words[0], true)]);
  180. }
  181. cr();
  182. out(tag('pre') + tag('code', attrs));
  183. out(esc(node.literal));
  184. out(tag('/code') + tag('/pre'));
  185. break;
  186. case 'XMLBlock':
  187. cr();
  188. out(node.literal);
  189. break;
  190. case 'HorizontalRule':
  191. cr();
  192. out(tag('hr', attrs, true));
  193. break;
  194. case 'ReferenceDef':
  195. break;
  196. default:
  197. throw("Unknown node type " + node.t);
  198. }
  199. }
  200. if (options.time) { console.timeEnd("rendering"); }
  201. return buffer;
  202. };
  203. var replaceUnsafeChar = function(s) {
  204. switch (s) {
  205. case '&':
  206. return '&amp;';
  207. case '<':
  208. return '&lt;';
  209. case '>':
  210. return '&gt;';
  211. case '"':
  212. return '&quot;';
  213. default:
  214. return s;
  215. }
  216. };
  217. var reNeedsEscaping = /[&<>"]/;
  218. // The XMLRenderer object.
  219. function XMLRenderer(options){
  220. return {
  221. // default options:
  222. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  223. // set to "<br />" to make them hard breaks
  224. // set to " " if you want to ignore line wrapping in source
  225. escape: function(s, preserve_entities) {
  226. if (reNeedsEscaping.test(s)) {
  227. if (preserve_entities) {
  228. return s.replace(/[&](?:[#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)|[&<>"]/gi, replaceUnsafeChar);
  229. } else {
  230. return s.replace(/[&<>"]/g, replaceUnsafeChar);
  231. }
  232. } else {
  233. return s;
  234. }
  235. },
  236. options: options || {},
  237. render: renderNodes
  238. };
  239. }
  240. module.exports = XMLRenderer;