diff options
Diffstat (limited to 'js/lib/html-renderer.js')
-rw-r--r-- | js/lib/html-renderer.js | 311 |
1 files changed, 191 insertions, 120 deletions
diff --git a/js/lib/html-renderer.js b/js/lib/html-renderer.js index f6efe32..e7953cf 100644 --- a/js/lib/html-renderer.js +++ b/js/lib/html-renderer.js @@ -1,6 +1,6 @@ -// Helper function to produce content in a pair of HTML tags. -var inTags = function(tag, attribs, contents, selfclosing) { - var result = '<' + tag; +// Helper function to produce an HTML tag. +var tag = function(name, attribs, selfclosing) { + var result = '<' + name; if (attribs) { var i = 0; var attrib; @@ -9,132 +9,207 @@ var inTags = function(tag, attribs, contents, selfclosing) { 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 'Text': - 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.children)); - case 'Strong': - return inTags('strong', [], this.renderInlines(inline.children)); - 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.children)); - case 'Image': - attrs = [['src', this.escape(inline.destination, true)], - ['alt', this.renderInlines(inline.children). - replace(/\<[^>]*alt="([^"]*)"[^>]*\>/g, '$1'). - replace(/\<[^>]*\>/g, '')]]; - 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 ""; - } -}; + if (selfclosing) + result += ' /'; -// 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]); - } + result += '>'; return result; }; -// Render a single block element. -var renderBlock = function(block, in_tight_list) { - var tag; - var attr; +var renderNodes = function(block) { + + var attrs; 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.children); + var tagname; + var walker = block.walker(); + var event, node, entering; + var buffer = []; + var disableTags = 0; + var grandparent; + var out = function(s) { + if (disableTags > 0) { + buffer.push(s.replace(/\<[^>]*\>/g, '')); } else { - return inTags('p', [], this.renderInlines(block.children)); - } - break; - case 'BlockQuote': - var filling = this.renderBlocks(block.children); - return inTags('blockquote', [], filling === '' ? this.innersep : - this.innersep + filling + this.innersep); - case 'ListItem': - var contents = this.renderBlocks(block.children, in_tight_list); - if (/^[<]/.test(contents)) { - contents = '\n' + contents; + buffer.push(s); } - if (/[>]$/.test(contents)) { - contents = contents + '\n'; + } + var esc = this.escape; + var cr = function() { + if (buffer.length > 0 && buffer[buffer.length - 1] !== '\n') { + out('\n'); } - return inTags('li', [], contents, false).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 'Header': - tag = 'h' + block.level; - return inTags(tag, [], this.renderInlines(block.children)); - case 'CodeBlock': - info_words = block.info ? 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)); + while (event = walker.next()) { + entering = event.entering; + node = event.node; + + switch (node.t) { + case 'Text': + out(esc(node.c)); + break; + + case 'Softbreak': + out(this.softbreak); + break; + + case 'Hardbreak': + out(tag('br', [], true)); + cr(); + break; + + case 'Emph': + out(tag(entering ? 'em' : '/em')); + break; + + case 'Strong': + out(tag(entering ? 'strong' : '/strong')); + break; + + case 'Emph': + out(tag(entering ? 'strong' : '/strong')); + break; + + case 'Html': + out(node.c); + break; + + case 'Link': + if (entering) { + attrs = [['href', esc(node.destination, true)]]; + if (node.title) { + attrs.push(['title', esc(node.title, true)]); + } + out(tag('a', attrs)); + } else { + out(tag('/a')); + } + break; + + case 'Image': + if (entering) { + if (disableTags == 0) { + out('<img src="' + esc(node.destination, true) + + '" alt="'); + } + disableTags += 1; + } else { + disableTags -= 1; + if (disableTags == 0) { + if (node.title) { + out('" title="' + esc(node.title, true)); + } + out('" />'); + } + } + break; + + case 'Code': + out(tag('code') + esc(node.c) + tag('/code')); + break; + + case 'Document': + break; + + case 'Paragraph': + grandparent = node.parent.parent; + if (grandparent !== null && + grandparent.t === 'List') { + if (grandparent.tight) + break; + } + if (entering) { + cr(); + out(tag('p')); + } else { + out(tag('/p')); + cr(); + } + break; + + case 'BlockQuote': + if (entering) { + cr(); + out(tag('blockquote')); + cr(); + } else { + cr(); + out(tag('/blockquote')); + cr(); + } + break; + + case 'ListItem': + if (entering) { + out(tag('li')); + } else { + out(tag('/li')); + cr(); + } + break; + + case 'List': + tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol'; + if (entering) { + attr = (!node.list_data.start || node.list_data.start === 1) ? + [] : [['start', node.list_data.start.toString()]]; + cr(); + out(tag(tagname, attr)); + cr(); + } else { + cr(); + out(tag('/' + tagname)); + cr(); + } + break; + + case 'Header': + tagname = 'h' + node.level; + if (entering) { + cr(); + out(tag(tagname)); + } else { + out(tag('/' + tagname)); + cr(); + } + break; + + case 'CodeBlock': + info_words = node.info ? node.info.split(/ +/) : []; + attr = (info_words.length === 0 || info_words[0].length === 0) + ? [] : [['class', 'language-' + esc(info_words[0], true)]]; + cr(); + out(tag('pre') + tag('code', attr)); + out(this.escape(node.string_content)); + out(tag('/code') + tag('/pre')); + cr(); + break; + + case 'HtmlBlock': + cr(); + out(node.string_content); + cr(); + break; + + case 'HorizontalRule': + cr(); + out(tag('hr', [], true)); + cr(); + break; + + + case 'ReferenceDef': + break; + + default: + console.log("Unknown node type " + node.t); } + } - return result.join(this.blocksep); + return buffer.join(''); }; + // The HtmlRenderer object. function HtmlRenderer(){ return { @@ -157,11 +232,7 @@ function HtmlRenderer(){ .replace(/["]/g, '"'); } }, - renderInline: renderInline, - renderInlines: renderInlines, - renderBlock: renderBlock, - renderBlocks: renderBlocks, - render: renderBlock + render: renderNodes }; } |