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