- // Helper function to produce content in a pair of HTML tags.
- var inTags = function(tag, attribs, contents, selfclosing) {
- var result = '<' + tag;
- if (attribs) {
- var i = 0;
- var attrib;
- while ((attrib = attribs[i]) !== undefined) {
- result = result.concat(' ', attrib[0], '="', attrib[1], '"');
- i++;
- }
- }
- if (contents) {
- result = result.concat('>', contents, '</', tag, '>');
- } else if (selfclosing) {
- result = result + ' />';
- } else {
- result = result.concat('></', tag, '>');
- }
- return result;
- };
- // Render an inline element as HTML.
- var renderInline = function(inline) {
- var attrs;
- switch (inline.t) {
- case 'Str':
- return this.escape(inline.c);
- case 'Softbreak':
- return this.softbreak;
- case 'Hardbreak':
- return inTags('br',[],"",true) + '\n';
- case 'Emph':
- return inTags('em', [], this.renderInlines(inline.c));
- case 'Strong':
- return inTags('strong', [], this.renderInlines(inline.c));
- case 'Html':
- return inline.c;
- case 'Link':
- attrs = [['href', this.escape(inline.destination, true)]];
- if (inline.title) {
- attrs.push(['title', this.escape(inline.title, true)]);
- }
- return inTags('a', attrs, this.renderInlines(inline.label));
- case 'Image':
- attrs = [['src', this.escape(inline.destination, true)],
- ['alt', this.escape(this.renderInlines(inline.label))]];
- if (inline.title) {
- attrs.push(['title', this.escape(inline.title, true)]);
- }
- return inTags('img', attrs, "", true);
- case 'Code':
- return inTags('code', [], this.escape(inline.c));
- default:
- console.log("Unknown inline type " + inline.t);
- return "";
- }
- };
- // Render a list of inlines.
- var renderInlines = function(inlines) {
- var result = '';
- for (var i=0; i < inlines.length; i++) {
- result = result + this.renderInline(inlines[i]);
- }
- return result;
- };
- // Render a single block element.
- var renderBlock = function(block, in_tight_list) {
- var tag;
- var attr;
- var info_words;
- switch (block.t) {
- case 'Document':
- var whole_doc = this.renderBlocks(block.children);
- return (whole_doc === '' ? '' : whole_doc + '\n');
- case 'Paragraph':
- if (in_tight_list) {
- return this.renderInlines(block.inline_content);
- } else {
- return inTags('p', [], this.renderInlines(block.inline_content));
- }
- break;
- case 'BlockQuote':
- var filling = this.renderBlocks(block.children);
- return inTags('blockquote', [], filling === '' ? this.innersep :
- this.innersep + filling + this.innersep);
- case 'ListItem':
- return inTags('li', [], this.renderBlocks(block.children, in_tight_list).trim());
- case 'List':
- tag = block.list_data.type == 'Bullet' ? 'ul' : 'ol';
- attr = (!block.list_data.start || block.list_data.start == 1) ?
- [] : [['start', block.list_data.start.toString()]];
- return inTags(tag, attr, this.innersep +
- this.renderBlocks(block.children, block.tight) +
- this.innersep);
- case 'ATXHeader':
- case 'SetextHeader':
- tag = 'h' + block.level;
- return inTags(tag, [], this.renderInlines(block.inline_content));
- case 'IndentedCode':
- return inTags('pre', [],
- inTags('code', [], this.escape(block.string_content)));
- case 'FencedCode':
- info_words = block.info.split(/ +/);
- attr = info_words.length === 0 || info_words[0].length === 0 ?
- [] : [['class','language-' +
- this.escape(info_words[0],true)]];
- return inTags('pre', [],
- inTags('code', attr, this.escape(block.string_content)));
- case 'HtmlBlock':
- return block.string_content;
- case 'ReferenceDef':
- return "";
- case 'HorizontalRule':
- return inTags('hr',[],"",true);
- default:
- console.log("Unknown block type " + block.t);
- return "";
- }
- };
- // Render a list of block elements, separated by this.blocksep.
- var renderBlocks = function(blocks, in_tight_list) {
- var result = [];
- for (var i=0; i < blocks.length; i++) {
- if (blocks[i].t !== 'ReferenceDef') {
- result.push(this.renderBlock(blocks[i], in_tight_list));
- }
- }
- return result.join(this.blocksep);
- };
- // The HtmlRenderer object.
- function HtmlRenderer(){
- return {
- // default options:
- blocksep: '\n', // space between blocks
- innersep: '\n', // space between block container tag and contents
- softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
- // set to "<br />" to make them hard breaks
- // set to " " if you want to ignore line wrapping in source
- escape: function(s, preserve_entities) {
- if (preserve_entities) {
- return s.replace(/[&](?![#](x[a-f0-9]{1,8}|[0-9]{1,8});|[a-z][a-z0-9]{1,31};)/gi,'&')
- .replace(/[<]/g,'<')
- .replace(/[>]/g,'>')
- .replace(/["]/g,'"');
- } else {
- return s.replace(/[&]/g,'&')
- .replace(/[<]/g,'<')
- .replace(/[>]/g,'>')
- .replace(/["]/g,'"');
- }
- },
- renderInline: renderInline,
- renderInlines: renderInlines,
- renderBlock: renderBlock,
- renderBlocks: renderBlocks,
- render: renderBlock
- };
- }
- module.exports = HtmlRenderer;
|