diff options
Diffstat (limited to 'packages/markdown-it-14.1.0/lib/renderer.mjs')
-rw-r--r-- | packages/markdown-it-14.1.0/lib/renderer.mjs | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/packages/markdown-it-14.1.0/lib/renderer.mjs b/packages/markdown-it-14.1.0/lib/renderer.mjs new file mode 100644 index 0000000..a58914c --- /dev/null +++ b/packages/markdown-it-14.1.0/lib/renderer.mjs @@ -0,0 +1,322 @@ +/** + * 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. + **/ + +import { assign, unescapeAll, escapeHtml } from './common/utils.mjs' + +const default_rules = {} + +default_rules.code_inline = function (tokens, idx, options, env, slf) { + const token = tokens[idx] + + return '<code' + slf.renderAttrs(token) + '>' + + escapeHtml(token.content) + + '</code>' +} + +default_rules.code_block = function (tokens, idx, options, env, slf) { + const token = tokens[idx] + + return '<pre' + slf.renderAttrs(token) + '><code>' + + escapeHtml(tokens[idx].content) + + '</code></pre>\n' +} + +default_rules.fence = function (tokens, idx, options, env, slf) { + const token = tokens[idx] + const info = token.info ? unescapeAll(token.info).trim() : '' + let langName = '' + let langAttrs = '' + + if (info) { + const arr = info.split(/(\s+)/g) + langName = arr[0] + langAttrs = arr.slice(2).join('') + } + + let highlighted + if (options.highlight) { + highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content) + } else { + highlighted = escapeHtml(token.content) + } + + if (highlighted.indexOf('<pre') === 0) { + return highlighted + '\n' + } + + // If language exists, inject class gently, without modifying original token. + // May be, one day we will add .deepClone() for token and simplify this part, but + // now we prefer to keep things local. + if (info) { + const i = token.attrIndex('class') + const tmpAttrs = token.attrs ? token.attrs.slice() : [] + + if (i < 0) { + tmpAttrs.push(['class', options.langPrefix + langName]) + } else { + tmpAttrs[i] = tmpAttrs[i].slice() + tmpAttrs[i][1] += ' ' + options.langPrefix + langName + } + + // Fake token just to render attributes + const tmpToken = { + attrs: tmpAttrs + } + + return `<pre><code${slf.renderAttrs(tmpToken)}>${highlighted}</code></pre>\n` + } + + return `<pre><code${slf.renderAttrs(token)}>${highlighted}</code></pre>\n` +} + +default_rules.image = function (tokens, idx, options, env, slf) { + const 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 ? '<br />\n' : '<br>\n' +} +default_rules.softbreak = function (tokens, idx, options /*, env */) { + return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\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 '<b>'; }; + * md.renderer.rules.strong_close = function () { return '</b>'; }; + * + * 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.mjs) + * 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) { + let 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) { + const token = tokens[idx] + let result = '' + + // 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. `<img` + result += (token.nesting === -1 ? '</' : '<') + token.tag + + // Encode attributes, e.g. `<img src="foo"` + result += this.renderAttrs(token) + + // Add a slash for self-closing tags, e.g. `<img src="foo" /` + if (token.nesting === 0 && options.xhtmlOut) { + result += ' /' + } + + // Check if we need to add a newline after this tag + let needLf = false + if (token.block) { + needLf = true + + if (token.nesting === 1) { + if (idx + 1 < tokens.length) { + const nextToken = tokens[idx + 1] + + if (nextToken.type === 'inline' || nextToken.hidden) { + // Block-level tag containing an inline tag. + // + needLf = false + } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) { + // Opening tag + closing tag of the same type. E.g. `<li></li>`. + // + needLf = false + } + } + } + } + + result += needLf ? '>\n' : '>' + + return result +} + +/** + * Renderer.renderInline(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - 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) { + let result = '' + const rules = this.rules + + for (let i = 0, len = tokens.length; i < len; i++) { + const 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 render + * - 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) { + let result = '' + + for (let i = 0, len = tokens.length; i < len; i++) { + switch (tokens[i].type) { + case 'text': + result += tokens[i].content + break + case 'image': + result += this.renderInlineAsText(tokens[i].children, options, env) + break + case 'html_inline': + case 'html_block': + result += tokens[i].content + break + case 'softbreak': + case 'hardbreak': + result += '\n' + break + default: + // all other tokens are skipped + } + } + + return result +} + +/** + * Renderer.render(tokens, options, env) -> String + * - tokens (Array): list on block tokens to render + * - 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) { + let result = '' + const rules = this.rules + + for (let i = 0, len = tokens.length; i < len; i++) { + const type = tokens[i].type + + if (type === 'inline') { + result += this.renderInline(tokens[i].children, options, env) + } else if (typeof rules[type] !== 'undefined') { + result += rules[type](tokens, i, options, env, this) + } else { + result += this.renderToken(tokens, i, options, env) + } + } + + return result +} + +export default Renderer |