/**
* class Renderer
*
* Generates HTML from parsed token stream. Each instance has independent
* copy of rules. Those can be rewritten with ease. Also, you can add new
* rules if you create plugin and adds new token types.
**/
'use strict';
var assign = require('./common/utils').assign;
var unescapeAll = require('./common/utils').unescapeAll;
var escapeHtml = require('./common/utils').escapeHtml;
////////////////////////////////////////////////////////////////////////////////
var default_rules = {};
default_rules.code_inline = function (tokens, idx, options, env, slf) {
var token = tokens[idx];
return '' +
escapeHtml(tokens[idx].content) +
'
';
};
default_rules.code_block = function (tokens, idx, options, env, slf) {
var token = tokens[idx];
return '
' +
escapeHtml(tokens[idx].content) +
'
\n';
};
default_rules.fence = function (tokens, idx, options, env, slf) {
var token = tokens[idx],
info = token.info ? unescapeAll(token.info).trim() : '',
langName = '',
langAttrs = '',
highlighted, i, arr, tmpAttrs, tmpToken;
if (info) {
arr = info.split(/(\s+)/g);
langName = arr[0];
langAttrs = arr.slice(2).join('');
}
if (options.highlight) {
highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content);
} else {
highlighted = escapeHtml(token.content);
}
if (highlighted.indexOf(''
+ highlighted
+ '
\n';
}
return ''
+ highlighted
+ '
\n';
};
default_rules.image = function (tokens, idx, options, env, slf) {
var token = tokens[idx];
// "alt" attr MUST be set, even if empty. Because it's mandatory and
// should be placed on proper position for tests.
//
// Replace content with actual value
token.attrs[token.attrIndex('alt')][1] =
slf.renderInlineAsText(token.children, options, env);
return slf.renderToken(tokens, idx, options);
};
default_rules.hardbreak = function (tokens, idx, options /*, env */) {
return options.xhtmlOut ? '
\n' : '
\n';
};
default_rules.softbreak = function (tokens, idx, options /*, env */) {
return options.breaks ? (options.xhtmlOut ? '
\n' : '
\n') : '\n';
};
default_rules.text = function (tokens, idx /*, options, env */) {
return escapeHtml(tokens[idx].content);
};
default_rules.html_block = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
};
default_rules.html_inline = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
};
/**
* new Renderer()
*
* Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
**/
function Renderer() {
/**
* Renderer#rules -> Object
*
* Contains render rules for tokens. Can be updated and extended.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.renderer.rules.strong_open = function () { return ''; };
* md.renderer.rules.strong_close = function () { return ''; };
*
* var result = md.renderInline(...);
* ```
*
* Each rule is called as independent static function with fixed signature:
*
* ```javascript
* function my_token_render(tokens, idx, options, env, renderer) {
* // ...
* return renderedHTML;
* }
* ```
*
* See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
* for more details and examples.
**/
this.rules = assign({}, default_rules);
}
/**
* Renderer.renderAttrs(token) -> String
*
* Render token attributes to string.
**/
Renderer.prototype.renderAttrs = function renderAttrs(token) {
var i, l, result;
if (!token.attrs) { return ''; }
result = '';
for (i = 0, l = token.attrs.length; i < l; i++) {
result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"';
}
return result;
};
/**
* Renderer.renderToken(tokens, idx, options) -> String
* - tokens (Array): list of tokens
* - idx (Numbed): token index to render
* - options (Object): params of parser instance
*
* Default token renderer. Can be overriden by custom function
* in [[Renderer#rules]].
**/
Renderer.prototype.renderToken = function renderToken(tokens, idx, options) {
var nextToken,
result = '',
needLf = false,
token = tokens[idx];
// Tight list paragraphs
if (token.hidden) {
return '';
}
// Insert a newline between hidden paragraph and subsequent opening
// block-level tag.
//
// For example, here we should insert a newline before blockquote:
// - a
// >
//
if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) {
result += '\n';
}
// Add token name, e.g. ``.
//
needLf = false;
}
}
}
}
result += needLf ? '>\n' : '>';
return result;
};
/**
* Renderer.renderInline(tokens, options, env) -> String
* - tokens (Array): list on block tokens to renter
* - options (Object): params of parser instance
* - env (Object): additional data from parsed input (references, for example)
*
* The same as [[Renderer.render]], but for single token of `inline` type.
**/
Renderer.prototype.renderInline = function (tokens, options, env) {
var type,
result = '',
rules = this.rules;
for (var i = 0, len = tokens.length; i < len; i++) {
type = tokens[i].type;
if (typeof rules[type] !== 'undefined') {
result += rules[type](tokens, i, options, env, this);
} else {
result += this.renderToken(tokens, i, options);
}
}
return result;
};
/** internal
* Renderer.renderInlineAsText(tokens, options, env) -> String
* - tokens (Array): list on block tokens to renter
* - options (Object): params of parser instance
* - env (Object): additional data from parsed input (references, for example)
*
* Special kludge for image `alt` attributes to conform CommonMark spec.
* Don't try to use it! Spec requires to show `alt` content with stripped markup,
* instead of simple escaping.
**/
Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
var result = '';
for (var i = 0, len = tokens.length; i < len; i++) {
if (tokens[i].type === 'text') {
result += tokens[i].content;
} else if (tokens[i].type === 'image') {
result += this.renderInlineAsText(tokens[i].children, options, env);
}
}
return result;
};
/**
* Renderer.render(tokens, options, env) -> String
* - tokens (Array): list on block tokens to renter
* - options (Object): params of parser instance
* - env (Object): additional data from parsed input (references, for example)
*
* Takes token stream and generates HTML. Probably, you will never need to call
* this method directly.
**/
Renderer.prototype.render = function (tokens, options, env) {
var i, len, type,
result = '',
rules = this.rules;
for (i = 0, len = tokens.length; i < len; i++) {
type = tokens[i].type;
if (type === 'inline') {
result += this.renderInline(tokens[i].children, options, env);
} else if (typeof rules[type] !== 'undefined') {
result += rules[tokens[i].type](tokens, i, options, env, this);
} else {
result += this.renderToken(tokens, i, options, env);
}
}
return result;
};
module.exports = Renderer;