aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html.js
blob: e361fe1837e46d1b4c05e744b211c9ef65a094f0 (plain)
  1. "use strict";
  2. var escapeXml = require('./common').escapeXml;
  3. // Helper function to produce an HTML tag.
  4. var tag = function(name, attrs, selfclosing) {
  5. var result = '<' + name;
  6. if (attrs && attrs.length > 0) {
  7. var i = 0;
  8. var attrib;
  9. while ((attrib = attrs[i]) !== undefined) {
  10. result += ' ' + attrib[0] + '="' + attrib[1] + '"';
  11. i++;
  12. }
  13. }
  14. if (selfclosing) {
  15. result += ' /';
  16. }
  17. result += '>';
  18. return result;
  19. };
  20. var reHtmlTag = /\<[^>]*\>/;
  21. var renderNodes = function(block) {
  22. var attrs;
  23. var info_words;
  24. var tagname;
  25. var walker = block.walker();
  26. var event, node, entering;
  27. var buffer = "";
  28. var lastOut = "\n";
  29. var disableTags = 0;
  30. var grandparent;
  31. var out = function(s) {
  32. if (disableTags > 0) {
  33. buffer += s.replace(reHtmlTag, '');
  34. } else {
  35. buffer += s;
  36. }
  37. lastOut = s;
  38. };
  39. var esc = this.escape;
  40. var cr = function() {
  41. if (lastOut !== '\n') {
  42. buffer += '\n';
  43. lastOut = '\n';
  44. }
  45. };
  46. var options = this.options;
  47. if (options.time) { console.time("rendering"); }
  48. while ((event = walker.next())) {
  49. entering = event.entering;
  50. node = event.node;
  51. attrs = [];
  52. if (options.sourcepos) {
  53. var pos = node.sourcepos;
  54. if (pos) {
  55. attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
  56. String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
  57. String(pos[1][1])]);
  58. }
  59. }
  60. switch (node.t) {
  61. case 'Text':
  62. out(esc(node.literal));
  63. break;
  64. case 'Softbreak':
  65. out(this.softbreak);
  66. break;
  67. case 'Hardbreak':
  68. out(tag('br', [], true));
  69. cr();
  70. break;
  71. case 'Emph':
  72. out(tag(entering ? 'em' : '/em'));
  73. break;
  74. case 'Strong':
  75. out(tag(entering ? 'strong' : '/strong'));
  76. break;
  77. case 'Html':
  78. out(node.literal);
  79. break;
  80. case 'Link':
  81. if (entering) {
  82. attrs.push(['href', esc(node.destination, true)]);
  83. if (node.title) {
  84. attrs.push(['title', esc(node.title, true)]);
  85. }
  86. out(tag('a', attrs));
  87. } else {
  88. out(tag('/a'));
  89. }
  90. break;
  91. case 'Image':
  92. if (entering) {
  93. if (disableTags === 0) {
  94. out('<img src="' + esc(node.destination, true) +
  95. '" alt="');
  96. }
  97. disableTags += 1;
  98. } else {
  99. disableTags -= 1;
  100. if (disableTags === 0) {
  101. if (node.title) {
  102. out('" title="' + esc(node.title, true));
  103. }
  104. out('" />');
  105. }
  106. }
  107. break;
  108. case 'Code':
  109. out(tag('code') + esc(node.literal) + tag('/code'));
  110. break;
  111. case 'Document':
  112. break;
  113. case 'Paragraph':
  114. grandparent = node.parent.parent;
  115. if (grandparent !== null &&
  116. grandparent.t === 'List') {
  117. if (grandparent.list_data.tight) {
  118. break;
  119. }
  120. }
  121. if (entering) {
  122. cr();
  123. out(tag('p', attrs));
  124. } else {
  125. out(tag('/p'));
  126. cr();
  127. }
  128. break;
  129. case 'BlockQuote':
  130. if (entering) {
  131. cr();
  132. out(tag('blockquote', attrs));
  133. cr();
  134. } else {
  135. cr();
  136. out(tag('/blockquote'));
  137. cr();
  138. }
  139. break;
  140. case 'Item':
  141. if (entering) {
  142. out(tag('li', attrs));
  143. } else {
  144. out(tag('/li'));
  145. cr();
  146. }
  147. break;
  148. case 'List':
  149. tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol';
  150. if (entering) {
  151. if (node.list_data.start && node.list_data.start > 1) {
  152. attrs.push(['start', node.list_data.start.toString()]);
  153. }
  154. cr();
  155. out(tag(tagname, attrs));
  156. cr();
  157. } else {
  158. cr();
  159. out(tag('/' + tagname));
  160. cr();
  161. }
  162. break;
  163. case 'Header':
  164. tagname = 'h' + node.level;
  165. if (entering) {
  166. cr();
  167. out(tag(tagname, attrs));
  168. } else {
  169. out(tag('/' + tagname));
  170. cr();
  171. }
  172. break;
  173. case 'CodeBlock':
  174. info_words = node.info ? node.info.split(/ +/) : [];
  175. if (info_words.length > 0 && info_words[0].length > 0) {
  176. attrs.push(['class', 'language-' + esc(info_words[0], true)]);
  177. }
  178. cr();
  179. out(tag('pre') + tag('code', attrs));
  180. out(esc(node.literal));
  181. out(tag('/code') + tag('/pre'));
  182. cr();
  183. break;
  184. case 'HtmlBlock':
  185. cr();
  186. out(node.literal);
  187. cr();
  188. break;
  189. case 'HorizontalRule':
  190. cr();
  191. out(tag('hr', attrs, true));
  192. cr();
  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. // The HtmlRenderer object.
  204. function HtmlRenderer(options){
  205. return {
  206. // default options:
  207. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  208. // set to "<br />" to make them hard breaks
  209. // set to " " if you want to ignore line wrapping in source
  210. escape: escapeXml,
  211. options: options || {},
  212. render: renderNodes
  213. };
  214. }
  215. module.exports = HtmlRenderer;