aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html.js
blob: 9f0c4e3b686241cb892647d760caf580ac7f182e (plain)
  1. "use strict";
  2. // Helper function to produce an HTML 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 reHtmlTag = /\<[^>]*\>/;
  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 grandparent;
  30. var out = function(s) {
  31. if (disableTags > 0) {
  32. buffer += s.replace(reHtmlTag, '');
  33. } else {
  34. buffer += s;
  35. }
  36. lastOut = s;
  37. };
  38. var esc = this.escape;
  39. var cr = function() {
  40. if (lastOut !== '\n') {
  41. buffer += '\n';
  42. lastOut = '\n';
  43. }
  44. };
  45. var options = this.options;
  46. while ((event = walker.next())) {
  47. entering = event.entering;
  48. node = event.node;
  49. attrs = [];
  50. if (options.sourcepos) {
  51. var pos = node.sourcepos;
  52. if (pos) {
  53. attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
  54. String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
  55. String(pos[1][1])]);
  56. }
  57. }
  58. switch (node.t) {
  59. case 'Text':
  60. out(esc(node.literal));
  61. break;
  62. case 'Softbreak':
  63. out(this.softbreak);
  64. break;
  65. case 'Hardbreak':
  66. out(tag('br', [], true));
  67. cr();
  68. break;
  69. case 'Emph':
  70. out(tag(entering ? 'em' : '/em'));
  71. break;
  72. case 'Strong':
  73. out(tag(entering ? 'strong' : '/strong'));
  74. break;
  75. case 'Emph':
  76. out(tag(entering ? 'strong' : '/strong'));
  77. break;
  78. case 'Html':
  79. out(node.literal);
  80. break;
  81. case 'Link':
  82. if (entering) {
  83. attrs.push(['href', esc(node.destination, true)]);
  84. if (node.title) {
  85. attrs.push(['title', esc(node.title, true)]);
  86. }
  87. out(tag('a', attrs));
  88. } else {
  89. out(tag('/a'));
  90. }
  91. break;
  92. case 'Image':
  93. if (entering) {
  94. if (disableTags === 0) {
  95. out('<img src="' + esc(node.destination, true) +
  96. '" alt="');
  97. }
  98. disableTags += 1;
  99. } else {
  100. disableTags -= 1;
  101. if (disableTags === 0) {
  102. if (node.title) {
  103. out('" title="' + esc(node.title, true));
  104. }
  105. out('" />');
  106. }
  107. }
  108. break;
  109. case 'Code':
  110. out(tag('code') + esc(node.literal) + tag('/code'));
  111. break;
  112. case 'Document':
  113. break;
  114. case 'Paragraph':
  115. grandparent = node.parent.parent;
  116. if (grandparent !== null &&
  117. grandparent.t === 'List') {
  118. if (grandparent.list_data.tight) {
  119. break;
  120. }
  121. }
  122. if (entering) {
  123. cr();
  124. out(tag('p', attrs));
  125. } else {
  126. out(tag('/p'));
  127. cr();
  128. }
  129. break;
  130. case 'BlockQuote':
  131. if (entering) {
  132. cr();
  133. out(tag('blockquote', attrs));
  134. cr();
  135. } else {
  136. cr();
  137. out(tag('/blockquote'));
  138. cr();
  139. }
  140. break;
  141. case 'ListItem':
  142. if (entering) {
  143. out(tag('li', attrs));
  144. } else {
  145. out(tag('/li'));
  146. cr();
  147. }
  148. break;
  149. case 'List':
  150. tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol';
  151. if (entering) {
  152. if (node.list_data.start && node.list_data.start > 1) {
  153. attrs.push(['start', node.list_data.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));
  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. case 'ReferenceDef':
  196. break;
  197. default:
  198. throw("Unknown node type " + node.t);
  199. }
  200. }
  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 HtmlRenderer object.
  219. function HtmlRenderer(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 = HtmlRenderer;