aboutsummaryrefslogtreecommitdiff
path: root/js/lib/html-renderer.js
blob: 6eb96bcc324b9e0ed1730e7d4c6ba1b2998f673e (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.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. return inTags('li', [], this.renderBlocks(block.children, in_tight_list).trim());
  89. case 'List':
  90. tag = block.list_data.type == 'Bullet' ? 'ul' : 'ol';
  91. attr = (!block.list_data.start || block.list_data.start == 1) ?
  92. [] : [['start', block.list_data.start.toString()]];
  93. return inTags(tag, attr, this.innersep +
  94. this.renderBlocks(block.children, block.tight) +
  95. this.innersep);
  96. case 'ATXHeader':
  97. case 'SetextHeader':
  98. tag = 'h' + block.level;
  99. return inTags(tag, [], this.renderInlines(block.inline_content));
  100. case 'IndentedCode':
  101. return inTags('pre', [],
  102. inTags('code', [], this.escape(block.string_content)));
  103. case 'FencedCode':
  104. info_words = block.info.split(/ +/);
  105. attr = info_words.length === 0 || info_words[0].length === 0 ?
  106. [] : [['class','language-' +
  107. this.escape(info_words[0],true)]];
  108. return inTags('pre', [],
  109. inTags('code', attr, this.escape(block.string_content)));
  110. case 'HtmlBlock':
  111. return block.string_content;
  112. case 'ReferenceDef':
  113. return "";
  114. case 'HorizontalRule':
  115. return inTags('hr',[],"",true);
  116. default:
  117. console.log("Unknown block type " + block.t);
  118. return "";
  119. }
  120. };
  121. // Render a list of block elements, separated by this.blocksep.
  122. var renderBlocks = function(blocks, in_tight_list) {
  123. var result = [];
  124. for (var i=0; i < blocks.length; i++) {
  125. if (blocks[i].t !== 'ReferenceDef') {
  126. result.push(this.renderBlock(blocks[i], in_tight_list));
  127. }
  128. }
  129. return result.join(this.blocksep);
  130. };
  131. // The HtmlRenderer object.
  132. function HtmlRenderer(){
  133. return {
  134. // default options:
  135. blocksep: '\n', // space between blocks
  136. innersep: '\n', // space between block container tag and contents
  137. softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
  138. // set to "<br />" to make them hard breaks
  139. // set to " " if you want to ignore line wrapping in source
  140. escape: function(s, preserve_entities) {
  141. if (preserve_entities) {
  142. return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&amp;')
  143. .replace(/[<]/g,'&lt;')
  144. .replace(/[>]/g,'&gt;')
  145. .replace(/["]/g,'&quot;');
  146. } else {
  147. return s.replace(/[&]/g,'&amp;')
  148. .replace(/[<]/g,'&lt;')
  149. .replace(/[>]/g,'&gt;')
  150. .replace(/["]/g,'&quot;');
  151. }
  152. },
  153. renderInline: renderInline,
  154. renderInlines: renderInlines,
  155. renderBlock: renderBlock,
  156. renderBlocks: renderBlocks,
  157. render: renderBlock
  158. };
  159. }
  160. module.exports = HtmlRenderer;