aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html-renderer.js
blob: e1a606381cf1f853f6e5904a4e5aeeda879cf44e (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 'Str':
  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.escape(this.renderInlines(inline.label))]];
  46. if (inline.title) {
  47. attrs.push(['title', this.escape(inline.title, true)]);
  48. }
  49. return inTags('img', attrs, "", true);
  50. case 'Code':
  51. return inTags('code', [], this.escape(inline.c));
  52. default:
  53. console.log("Unknown inline type " + inline.t);
  54. return "";
  55. }
  56. };
  57. // Render a list of inlines.
  58. var renderInlines = function(inlines) {
  59. var result = '';
  60. for (var i=0; i < inlines.length; i++) {
  61. result = result + this.renderInline(inlines[i]);
  62. }
  63. return result;
  64. };
  65. // Render a single block element.
  66. var renderBlock = function(block, in_tight_list) {
  67. var tag;
  68. var attr;
  69. var info_words;
  70. switch (block.t) {
  71. case 'Document':
  72. var whole_doc = this.renderBlocks(block.children);
  73. return (whole_doc === '' ? '' : whole_doc + '\n');
  74. case 'Paragraph':
  75. if (in_tight_list) {
  76. return this.renderInlines(block.inline_content);
  77. } else {
  78. return inTags('p', [], this.renderInlines(block.inline_content));
  79. }
  80. break;
  81. case 'BlockQuote':
  82. var filling = this.renderBlocks(block.children);
  83. return inTags('blockquote', [], filling === '' ? this.innersep :
  84. this.innersep + filling + this.innersep);
  85. case 'ListItem':
  86. return inTags('li', [], this.renderBlocks(block.children, in_tight_list).trim());
  87. case 'List':
  88. tag = block.list_data.type == 'Bullet' ? 'ul' : 'ol';
  89. attr = (!block.list_data.start || block.list_data.start == 1) ?
  90. [] : [['start', block.list_data.start.toString()]];
  91. return inTags(tag, attr, this.innersep +
  92. this.renderBlocks(block.children, block.tight) +
  93. this.innersep);
  94. case 'ATXHeader':
  95. case 'SetextHeader':
  96. tag = 'h' + block.level;
  97. return inTags(tag, [], this.renderInlines(block.inline_content));
  98. case 'IndentedCode':
  99. return inTags('pre', [],
  100. inTags('code', [], this.escape(block.string_content)));
  101. case 'FencedCode':
  102. info_words = block.info.split(/ +/);
  103. attr = info_words.length === 0 || info_words[0].length === 0 ?
  104. [] : [['class','language-' +
  105. this.escape(info_words[0],true)]];
  106. return inTags('pre', [],
  107. inTags('code', attr, this.escape(block.string_content)));
  108. case 'HtmlBlock':
  109. return block.string_content;
  110. case 'ReferenceDef':
  111. return "";
  112. case 'HorizontalRule':
  113. return inTags('hr',[],"",true);
  114. default:
  115. console.log("Unknown block type " + block.t);
  116. return "";
  117. }
  118. };
  119. // Render a list of block elements, separated by this.blocksep.
  120. var renderBlocks = function(blocks, in_tight_list) {
  121. var result = [];
  122. for (var i=0; i < blocks.length; i++) {
  123. if (blocks[i].t !== 'ReferenceDef') {
  124. result.push(this.renderBlock(blocks[i], in_tight_list));
  125. }
  126. }
  127. return result.join(this.blocksep);
  128. };
  129. // The HtmlRenderer object.
  130. function HtmlRenderer(){
  131. return {
  132. // default options:
  133. blocksep: '\n', // space between blocks
  134. innersep: '\n', // space between block container tag and contents
  135. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  136. // set to "<br />" to make them hard breaks
  137. // set to " " if you want to ignore line wrapping in source
  138. escape: function(s, preserve_entities) {
  139. if (preserve_entities) {
  140. return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&amp;')
  141. .replace(/[<]/g,'&lt;')
  142. .replace(/[>]/g,'&gt;')
  143. .replace(/["]/g,'&quot;');
  144. } else {
  145. return s.replace(/[&]/g,'&amp;')
  146. .replace(/[<]/g,'&lt;')
  147. .replace(/[>]/g,'&gt;')
  148. .replace(/["]/g,'&quot;');
  149. }
  150. },
  151. renderInline: renderInline,
  152. renderInlines: renderInlines,
  153. renderBlock: renderBlock,
  154. renderBlocks: renderBlocks,
  155. render: renderBlock
  156. };
  157. }
  158. module.exports = HtmlRenderer;