aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html-renderer.js
blob: 455da22290053cdb085128ca5b9f3f6369fb2807 (plain)
  1. // Helper function to produce content in a pair of HTML tags.
  2. var inTags = function(tag, attribs, contents, selfclosing) {
  3. var result = '<' + tag;
  4. if (attribs) {
  5. var i = 0;
  6. var attrib;
  7. while ((attrib = attribs[i]) !== undefined) {
  8. result = result.concat(' ', attrib[0], '="', attrib[1], '"');
  9. i++;
  10. }
  11. }
  12. if (contents) {
  13. result = result.concat('>', contents, '</', tag, '>');
  14. } else if (selfclosing) {
  15. result = result + ' />';
  16. } else {
  17. result = result.concat('></', tag, '>');
  18. }
  19. return result;
  20. };
  21. // Render an inline element as HTML.
  22. var renderInline = function(inline) {
  23. var attrs;
  24. switch (inline.t) {
  25. case 'Text':
  26. return this.escape(inline.c);
  27. case 'Softbreak':
  28. return this.softbreak;
  29. case 'Hardbreak':
  30. return inTags('br', [], "", true) + '\n';
  31. case 'Emph':
  32. return inTags('em', [], this.renderInlines(inline.c));
  33. case 'Strong':
  34. return inTags('strong', [], this.renderInlines(inline.c));
  35. case 'Html':
  36. return inline.c;
  37. case 'Link':
  38. attrs = [['href', this.escape(inline.destination, true)]];
  39. if (inline.title) {
  40. attrs.push(['title', this.escape(inline.title, true)]);
  41. }
  42. return inTags('a', attrs, this.renderInlines(inline.label));
  43. case 'Image':
  44. attrs = [['src', this.escape(inline.destination, true)],
  45. ['alt', this.renderInlines(inline.label).
  46. replace(/\<[^>]*alt="([^"]*)"[^>]*\>/g, '$1').
  47. replace(/\<[^>]*\>/g,'')]];
  48. if (inline.title) {
  49. attrs.push(['title', this.escape(inline.title, true)]);
  50. }
  51. return inTags('img', attrs, "", true);
  52. case 'Code':
  53. return inTags('code', [], this.escape(inline.c));
  54. default:
  55. console.log("Unknown inline type " + inline.t);
  56. return "";
  57. }
  58. };
  59. // Render a list of inlines.
  60. var renderInlines = function(inlines) {
  61. var result = '';
  62. for (var i = 0; i < inlines.length; i++) {
  63. result = result + this.renderInline(inlines[i]);
  64. }
  65. return result;
  66. };
  67. // Render a single block element.
  68. var renderBlock = function(block, in_tight_list) {
  69. var tag;
  70. var attr;
  71. var info_words;
  72. switch (block.t) {
  73. case 'Document':
  74. var whole_doc = this.renderBlocks(block.children);
  75. return (whole_doc === '' ? '' : whole_doc + '\n');
  76. case 'Paragraph':
  77. if (in_tight_list) {
  78. return this.renderInlines(block.inline_content);
  79. } else {
  80. return inTags('p', [], this.renderInlines(block.inline_content));
  81. }
  82. break;
  83. case 'BlockQuote':
  84. var filling = this.renderBlocks(block.children);
  85. return inTags('blockquote', [], filling === '' ? this.innersep :
  86. this.innersep + filling + this.innersep);
  87. case 'ListItem':
  88. var contents = this.renderBlocks(block.children, in_tight_list);
  89. if (/^[<]/.test(contents)) {
  90. contents = '\n' + contents;
  91. }
  92. if (/[>]$/.test(contents)) {
  93. contents = contents + '\n';
  94. }
  95. return inTags('li', [], contents, false).trim();
  96. case 'List':
  97. tag = block.list_data.type === 'Bullet' ? 'ul' : 'ol';
  98. attr = (!block.list_data.start || block.list_data.start === 1) ?
  99. [] : [['start', block.list_data.start.toString()]];
  100. return inTags(tag, attr, this.innersep +
  101. this.renderBlocks(block.children, block.tight) +
  102. this.innersep);
  103. case 'Header':
  104. tag = 'h' + block.level;
  105. return inTags(tag, [], this.renderInlines(block.inline_content));
  106. case 'CodeBlock':
  107. info_words = block.info ? block.info.split(/ +/) : [];
  108. attr = (info_words.length === 0 || info_words[0].length === 0) ?
  109. [] : [['class', 'language-' + this.escape(info_words[0], true)]];
  110. return inTags('pre', [],
  111. inTags('code', attr, this.escape(block.string_content)));
  112. case 'HtmlBlock':
  113. return block.string_content;
  114. case 'ReferenceDef':
  115. return "";
  116. case 'HorizontalRule':
  117. return inTags('hr', [], "", true);
  118. default:
  119. console.log("Unknown block type " + block.t);
  120. return "";
  121. }
  122. };
  123. // Render a list of block elements, separated by this.blocksep.
  124. var renderBlocks = function(blocks, in_tight_list) {
  125. var result = [];
  126. for (var i = 0; i < blocks.length; i++) {
  127. if (blocks[i].t !== 'ReferenceDef') {
  128. result.push(this.renderBlock(blocks[i], in_tight_list));
  129. }
  130. }
  131. return result.join(this.blocksep);
  132. };
  133. // The HtmlRenderer object.
  134. function HtmlRenderer(){
  135. return {
  136. // default options:
  137. blocksep: '\n', // space between blocks
  138. innersep: '\n', // space between block container tag and contents
  139. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  140. // set to "<br />" to make them hard breaks
  141. // set to " " if you want to ignore line wrapping in source
  142. escape: function(s, preserve_entities) {
  143. if (preserve_entities) {
  144. return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&amp;')
  145. .replace(/[<]/g,'&lt;')
  146. .replace(/[>]/g,'&gt;')
  147. .replace(/["]/g,'&quot;');
  148. } else {
  149. return s.replace(/[&]/g,'&amp;')
  150. .replace(/[<]/g,'&lt;')
  151. .replace(/[>]/g,'&gt;')
  152. .replace(/["]/g,'&quot;');
  153. }
  154. },
  155. renderInline: renderInline,
  156. renderInlines: renderInlines,
  157. renderBlock: renderBlock,
  158. renderBlocks: renderBlocks,
  159. render: renderBlock
  160. };
  161. }
  162. module.exports = HtmlRenderer;