renderer.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. /**
  2. * class Renderer
  3. *
  4. * Generates HTML from parsed token stream. Each instance has independent
  5. * copy of rules. Those can be rewritten with ease. Also, you can add new
  6. * rules if you create plugin and adds new token types.
  7. **/
  8. 'use strict';
  9. var assign = require('./common/utils').assign;
  10. var unescapeAll = require('./common/utils').unescapeAll;
  11. var escapeHtml = require('./common/utils').escapeHtml;
  12. ////////////////////////////////////////////////////////////////////////////////
  13. var default_rules = {};
  14. default_rules.code_inline = function (tokens, idx, options, env, slf) {
  15. var token = tokens[idx];
  16. return '<code' + slf.renderAttrs(token) + '>' +
  17. escapeHtml(tokens[idx].content) +
  18. '</code>';
  19. };
  20. default_rules.code_block = function (tokens, idx, options, env, slf) {
  21. var token = tokens[idx];
  22. return '<pre' + slf.renderAttrs(token) + '><code>' +
  23. escapeHtml(tokens[idx].content) +
  24. '</code></pre>\n';
  25. };
  26. default_rules.fence = function (tokens, idx, options, env, slf) {
  27. var token = tokens[idx],
  28. info = token.info ? unescapeAll(token.info).trim() : '',
  29. langName = '',
  30. highlighted, i, tmpAttrs, tmpToken;
  31. if (info) {
  32. langName = info.split(/\s+/g)[0];
  33. }
  34. if (options.highlight) {
  35. highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);
  36. } else {
  37. highlighted = escapeHtml(token.content);
  38. }
  39. if (highlighted.indexOf('<pre') === 0) {
  40. return highlighted + '\n';
  41. }
  42. // If language exists, inject class gently, without modifying original token.
  43. // May be, one day we will add .clone() for token and simplify this part, but
  44. // now we prefer to keep things local.
  45. if (info) {
  46. i = token.attrIndex('class');
  47. tmpAttrs = token.attrs ? token.attrs.slice() : [];
  48. if (i < 0) {
  49. tmpAttrs.push([ 'class', options.langPrefix + langName ]);
  50. } else {
  51. tmpAttrs[i][1] += ' ' + options.langPrefix + langName;
  52. }
  53. // Fake token just to render attributes
  54. tmpToken = {
  55. attrs: tmpAttrs
  56. };
  57. return '<pre><code' + slf.renderAttrs(tmpToken) + '>'
  58. + highlighted
  59. + '</code></pre>\n';
  60. }
  61. return '<pre><code' + slf.renderAttrs(token) + '>'
  62. + highlighted
  63. + '</code></pre>\n';
  64. };
  65. default_rules.image = function (tokens, idx, options, env, slf) {
  66. var token = tokens[idx];
  67. // "alt" attr MUST be set, even if empty. Because it's mandatory and
  68. // should be placed on proper position for tests.
  69. //
  70. // Replace content with actual value
  71. token.attrs[token.attrIndex('alt')][1] =
  72. slf.renderInlineAsText(token.children, options, env);
  73. return slf.renderToken(tokens, idx, options);
  74. };
  75. default_rules.hardbreak = function (tokens, idx, options /*, env */) {
  76. return options.xhtmlOut ? '<br />\n' : '<br>\n';
  77. };
  78. default_rules.softbreak = function (tokens, idx, options /*, env */) {
  79. return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n';
  80. };
  81. default_rules.text = function (tokens, idx /*, options, env */) {
  82. return escapeHtml(tokens[idx].content);
  83. };
  84. default_rules.html_block = function (tokens, idx /*, options, env */) {
  85. return tokens[idx].content;
  86. };
  87. default_rules.html_inline = function (tokens, idx /*, options, env */) {
  88. return tokens[idx].content;
  89. };
  90. /**
  91. * new Renderer()
  92. *
  93. * Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
  94. **/
  95. function Renderer() {
  96. /**
  97. * Renderer#rules -> Object
  98. *
  99. * Contains render rules for tokens. Can be updated and extended.
  100. *
  101. * ##### Example
  102. *
  103. * ```javascript
  104. * var md = require('markdown-it')();
  105. *
  106. * md.renderer.rules.strong_open = function () { return '<b>'; };
  107. * md.renderer.rules.strong_close = function () { return '</b>'; };
  108. *
  109. * var result = md.renderInline(...);
  110. * ```
  111. *
  112. * Each rule is called as independent static function with fixed signature:
  113. *
  114. * ```javascript
  115. * function my_token_render(tokens, idx, options, env, renderer) {
  116. * // ...
  117. * return renderedHTML;
  118. * }
  119. * ```
  120. *
  121. * See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
  122. * for more details and examples.
  123. **/
  124. this.rules = assign({}, default_rules);
  125. }
  126. /**
  127. * Renderer.renderAttrs(token) -> String
  128. *
  129. * Render token attributes to string.
  130. **/
  131. Renderer.prototype.renderAttrs = function renderAttrs(token) {
  132. var i, l, result;
  133. if (!token.attrs) { return ''; }
  134. result = '';
  135. for (i = 0, l = token.attrs.length; i < l; i++) {
  136. result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"';
  137. }
  138. return result;
  139. };
  140. /**
  141. * Renderer.renderToken(tokens, idx, options) -> String
  142. * - tokens (Array): list of tokens
  143. * - idx (Numbed): token index to render
  144. * - options (Object): params of parser instance
  145. *
  146. * Default token renderer. Can be overriden by custom function
  147. * in [[Renderer#rules]].
  148. **/
  149. Renderer.prototype.renderToken = function renderToken(tokens, idx, options) {
  150. var nextToken,
  151. result = '',
  152. needLf = false,
  153. token = tokens[idx];
  154. // Tight list paragraphs
  155. if (token.hidden) {
  156. return '';
  157. }
  158. // Insert a newline between hidden paragraph and subsequent opening
  159. // block-level tag.
  160. //
  161. // For example, here we should insert a newline before blockquote:
  162. // - a
  163. // >
  164. //
  165. if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) {
  166. result += '\n';
  167. }
  168. // Add token name, e.g. `<img`
  169. result += (token.nesting === -1 ? '</' : '<') + token.tag;
  170. // Encode attributes, e.g. `<img src="foo"`
  171. result += this.renderAttrs(token);
  172. // Add a slash for self-closing tags, e.g. `<img src="foo" /`
  173. if (token.nesting === 0 && options.xhtmlOut) {
  174. result += ' /';
  175. }
  176. // Check if we need to add a newline after this tag
  177. if (token.block) {
  178. needLf = true;
  179. if (token.nesting === 1) {
  180. if (idx + 1 < tokens.length) {
  181. nextToken = tokens[idx + 1];
  182. if (nextToken.type === 'inline' || nextToken.hidden) {
  183. // Block-level tag containing an inline tag.
  184. //
  185. needLf = false;
  186. } else if (nextToken.nesting === -1 && nextToken.tag === token.tag) {
  187. // Opening tag + closing tag of the same type. E.g. `<li></li>`.
  188. //
  189. needLf = false;
  190. }
  191. }
  192. }
  193. }
  194. result += needLf ? '>\n' : '>';
  195. return result;
  196. };
  197. /**
  198. * Renderer.renderInline(tokens, options, env) -> String
  199. * - tokens (Array): list on block tokens to renter
  200. * - options (Object): params of parser instance
  201. * - env (Object): additional data from parsed input (references, for example)
  202. *
  203. * The same as [[Renderer.render]], but for single token of `inline` type.
  204. **/
  205. Renderer.prototype.renderInline = function (tokens, options, env) {
  206. var type,
  207. result = '',
  208. rules = this.rules;
  209. for (var i = 0, len = tokens.length; i < len; i++) {
  210. type = tokens[i].type;
  211. if (typeof rules[type] !== 'undefined') {
  212. result += rules[type](tokens, i, options, env, this);
  213. } else {
  214. result += this.renderToken(tokens, i, options);
  215. }
  216. }
  217. return result;
  218. };
  219. /** internal
  220. * Renderer.renderInlineAsText(tokens, options, env) -> String
  221. * - tokens (Array): list on block tokens to renter
  222. * - options (Object): params of parser instance
  223. * - env (Object): additional data from parsed input (references, for example)
  224. *
  225. * Special kludge for image `alt` attributes to conform CommonMark spec.
  226. * Don't try to use it! Spec requires to show `alt` content with stripped markup,
  227. * instead of simple escaping.
  228. **/
  229. Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
  230. var result = '';
  231. for (var i = 0, len = tokens.length; i < len; i++) {
  232. if (tokens[i].type === 'text') {
  233. result += tokens[i].content;
  234. } else if (tokens[i].type === 'image') {
  235. result += this.renderInlineAsText(tokens[i].children, options, env);
  236. }
  237. }
  238. return result;
  239. };
  240. /**
  241. * Renderer.render(tokens, options, env) -> String
  242. * - tokens (Array): list on block tokens to renter
  243. * - options (Object): params of parser instance
  244. * - env (Object): additional data from parsed input (references, for example)
  245. *
  246. * Takes token stream and generates HTML. Probably, you will never need to call
  247. * this method directly.
  248. **/
  249. Renderer.prototype.render = function (tokens, options, env) {
  250. var i, len, type,
  251. result = '',
  252. rules = this.rules;
  253. for (i = 0, len = tokens.length; i < len; i++) {
  254. type = tokens[i].type;
  255. if (type === 'inline') {
  256. result += this.renderInline(tokens[i].children, options, env);
  257. } else if (typeof rules[type] !== 'undefined') {
  258. result += rules[tokens[i].type](tokens, i, options, env, this);
  259. } else {
  260. result += this.renderToken(tokens, i, options, env);
  261. }
  262. }
  263. return result;
  264. };
  265. module.exports = Renderer;