aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html.js
blob: 74a854c60e02ab90f0b754e37dd242fd82e5c5fc (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.type) {
  61. case 'Text':
  62. out(esc(node.literal, false));
  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, false) + tag('/code'));
  110. break;
  111. case 'Document':
  112. break;
  113. case 'Paragraph':
  114. grandparent = node.parent.parent;
  115. if (grandparent !== null &&
  116. grandparent.type === 'List') {
  117. if (grandparent.listTight) {
  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.listType === 'Bullet' ? 'ul' : 'ol';
  150. if (entering) {
  151. var start = node.listStart;
  152. if (start && start > 1) {
  153. attrs.push(['start', start.toString()]);
  154. }
  155. cr();
  156. out(tag(tagname, attrs));
  157. cr();
  158. } else {
  159. cr();
  160. out(tag('/' + tagname));
  161. cr();
  162. }
  163. break;
  164. case 'Header':
  165. tagname = 'h' + node.level;
  166. if (entering) {
  167. cr();
  168. out(tag(tagname, attrs));
  169. } else {
  170. out(tag('/' + tagname));
  171. cr();
  172. }
  173. break;
  174. case 'CodeBlock':
  175. info_words = node.info ? node.info.split(/ +/) : [];
  176. if (info_words.length > 0 && info_words[0].length > 0) {
  177. attrs.push(['class', 'language-' + esc(info_words[0], true)]);
  178. }
  179. cr();
  180. out(tag('pre') + tag('code', attrs));
  181. out(esc(node.literal, false));
  182. out(tag('/code') + tag('/pre'));
  183. cr();
  184. break;
  185. case 'HtmlBlock':
  186. cr();
  187. out(node.literal);
  188. cr();
  189. break;
  190. case 'HorizontalRule':
  191. cr();
  192. out(tag('hr', attrs, true));
  193. cr();
  194. break;
  195. default:
  196. throw "Unknown node type " + node.type;
  197. }
  198. }
  199. if (options.time) { console.timeEnd("rendering"); }
  200. return buffer;
  201. };
  202. // The HtmlRenderer object.
  203. function HtmlRenderer(options){
  204. return {
  205. // default options:
  206. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  207. // set to "<br />" to make them hard breaks
  208. // set to " " if you want to ignore line wrapping in source
  209. escape: escapeXml,
  210. options: options || {},
  211. render: renderNodes
  212. };
  213. }
  214. module.exports = HtmlRenderer;