marked.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. import { Lexer } from './Lexer.js';
  2. import { Parser } from './Parser.js';
  3. import { Tokenizer } from './Tokenizer.js';
  4. import { Renderer } from './Renderer.js';
  5. import { TextRenderer } from './TextRenderer.js';
  6. import { Slugger } from './Slugger.js';
  7. import {
  8. merge,
  9. checkSanitizeDeprecation,
  10. escape
  11. } from './helpers.js';
  12. import {
  13. getDefaults,
  14. changeDefaults,
  15. defaults
  16. } from './defaults.js';
  17. /**
  18. * Marked
  19. */
  20. export function marked(src, opt, callback) {
  21. // throw error in case of non string input
  22. if (typeof src === 'undefined' || src === null) {
  23. throw new Error('marked(): input parameter is undefined or null');
  24. }
  25. if (typeof src !== 'string') {
  26. throw new Error('marked(): input parameter is of type '
  27. + Object.prototype.toString.call(src) + ', string expected');
  28. }
  29. if (typeof opt === 'function') {
  30. callback = opt;
  31. opt = null;
  32. }
  33. opt = merge({}, marked.defaults, opt || {});
  34. checkSanitizeDeprecation(opt);
  35. if (callback) {
  36. const highlight = opt.highlight;
  37. let tokens;
  38. try {
  39. tokens = Lexer.lex(src, opt);
  40. } catch (e) {
  41. return callback(e);
  42. }
  43. const done = function(err) {
  44. let out;
  45. if (!err) {
  46. try {
  47. if (opt.walkTokens) {
  48. marked.walkTokens(tokens, opt.walkTokens);
  49. }
  50. out = Parser.parse(tokens, opt);
  51. } catch (e) {
  52. err = e;
  53. }
  54. }
  55. opt.highlight = highlight;
  56. return err
  57. ? callback(err)
  58. : callback(null, out);
  59. };
  60. if (!highlight || highlight.length < 3) {
  61. return done();
  62. }
  63. delete opt.highlight;
  64. if (!tokens.length) return done();
  65. let pending = 0;
  66. marked.walkTokens(tokens, function(token) {
  67. if (token.type === 'code') {
  68. pending++;
  69. setTimeout(() => {
  70. highlight(token.text, token.lang, function(err, code) {
  71. if (err) {
  72. return done(err);
  73. }
  74. if (code != null && code !== token.text) {
  75. token.text = code;
  76. token.escaped = true;
  77. }
  78. pending--;
  79. if (pending === 0) {
  80. done();
  81. }
  82. });
  83. }, 0);
  84. }
  85. });
  86. if (pending === 0) {
  87. done();
  88. }
  89. return;
  90. }
  91. try {
  92. const tokens = Lexer.lex(src, opt);
  93. if (opt.walkTokens) {
  94. marked.walkTokens(tokens, opt.walkTokens);
  95. }
  96. return Parser.parse(tokens, opt);
  97. } catch (e) {
  98. e.message += '\nPlease report this to https://github.com/markedjs/marked.';
  99. if (opt.silent) {
  100. return '<p>An error occurred:</p><pre>'
  101. + escape(e.message + '', true)
  102. + '</pre>';
  103. }
  104. throw e;
  105. }
  106. }
  107. /**
  108. * Options
  109. */
  110. marked.options =
  111. marked.setOptions = function(opt) {
  112. merge(marked.defaults, opt);
  113. changeDefaults(marked.defaults);
  114. return marked;
  115. };
  116. marked.getDefaults = getDefaults;
  117. marked.defaults = defaults;
  118. /**
  119. * Use Extension
  120. */
  121. marked.use = function(...args) {
  122. const opts = merge({}, ...args);
  123. const extensions = marked.defaults.extensions || { renderers: {}, childTokens: {} };
  124. let hasExtensions;
  125. args.forEach((pack) => {
  126. // ==-- Parse "addon" extensions --== //
  127. if (pack.extensions) {
  128. hasExtensions = true;
  129. pack.extensions.forEach((ext) => {
  130. if (!ext.name) {
  131. throw new Error('extension name required');
  132. }
  133. if (ext.renderer) { // Renderer extensions
  134. const prevRenderer = extensions.renderers ? extensions.renderers[ext.name] : null;
  135. if (prevRenderer) {
  136. // Replace extension with func to run new extension but fall back if false
  137. extensions.renderers[ext.name] = function(...args) {
  138. let ret = ext.renderer.apply(this, args);
  139. if (ret === false) {
  140. ret = prevRenderer.apply(this, args);
  141. }
  142. return ret;
  143. };
  144. } else {
  145. extensions.renderers[ext.name] = ext.renderer;
  146. }
  147. }
  148. if (ext.tokenizer) { // Tokenizer Extensions
  149. if (!ext.level || (ext.level !== 'block' && ext.level !== 'inline')) {
  150. throw new Error("extension level must be 'block' or 'inline'");
  151. }
  152. if (extensions[ext.level]) {
  153. extensions[ext.level].unshift(ext.tokenizer);
  154. } else {
  155. extensions[ext.level] = [ext.tokenizer];
  156. }
  157. if (ext.start) { // Function to check for start of token
  158. if (ext.level === 'block') {
  159. if (extensions.startBlock) {
  160. extensions.startBlock.push(ext.start);
  161. } else {
  162. extensions.startBlock = [ext.start];
  163. }
  164. } else if (ext.level === 'inline') {
  165. if (extensions.startInline) {
  166. extensions.startInline.push(ext.start);
  167. } else {
  168. extensions.startInline = [ext.start];
  169. }
  170. }
  171. }
  172. }
  173. if (ext.childTokens) { // Child tokens to be visited by walkTokens
  174. extensions.childTokens[ext.name] = ext.childTokens;
  175. }
  176. });
  177. }
  178. // ==-- Parse "overwrite" extensions --== //
  179. if (pack.renderer) {
  180. const renderer = marked.defaults.renderer || new Renderer();
  181. for (const prop in pack.renderer) {
  182. const prevRenderer = renderer[prop];
  183. // Replace renderer with func to run extension, but fall back if false
  184. renderer[prop] = (...args) => {
  185. let ret = pack.renderer[prop].apply(renderer, args);
  186. if (ret === false) {
  187. ret = prevRenderer.apply(renderer, args);
  188. }
  189. return ret;
  190. };
  191. }
  192. opts.renderer = renderer;
  193. }
  194. if (pack.tokenizer) {
  195. const tokenizer = marked.defaults.tokenizer || new Tokenizer();
  196. for (const prop in pack.tokenizer) {
  197. const prevTokenizer = tokenizer[prop];
  198. // Replace tokenizer with func to run extension, but fall back if false
  199. tokenizer[prop] = (...args) => {
  200. let ret = pack.tokenizer[prop].apply(tokenizer, args);
  201. if (ret === false) {
  202. ret = prevTokenizer.apply(tokenizer, args);
  203. }
  204. return ret;
  205. };
  206. }
  207. opts.tokenizer = tokenizer;
  208. }
  209. // ==-- Parse WalkTokens extensions --== //
  210. if (pack.walkTokens) {
  211. const walkTokens = marked.defaults.walkTokens;
  212. opts.walkTokens = function(token) {
  213. pack.walkTokens.call(this, token);
  214. if (walkTokens) {
  215. walkTokens.call(this, token);
  216. }
  217. };
  218. }
  219. if (hasExtensions) {
  220. opts.extensions = extensions;
  221. }
  222. marked.setOptions(opts);
  223. });
  224. };
  225. /**
  226. * Run callback for every token
  227. */
  228. marked.walkTokens = function(tokens, callback) {
  229. for (const token of tokens) {
  230. callback.call(marked, token);
  231. switch (token.type) {
  232. case 'table': {
  233. for (const cell of token.header) {
  234. marked.walkTokens(cell.tokens, callback);
  235. }
  236. for (const row of token.rows) {
  237. for (const cell of row) {
  238. marked.walkTokens(cell.tokens, callback);
  239. }
  240. }
  241. break;
  242. }
  243. case 'list': {
  244. marked.walkTokens(token.items, callback);
  245. break;
  246. }
  247. default: {
  248. if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions
  249. marked.defaults.extensions.childTokens[token.type].forEach(function(childTokens) {
  250. marked.walkTokens(token[childTokens], callback);
  251. });
  252. } else if (token.tokens) {
  253. marked.walkTokens(token.tokens, callback);
  254. }
  255. }
  256. }
  257. }
  258. };
  259. /**
  260. * Parse Inline
  261. */
  262. marked.parseInline = function(src, opt) {
  263. // throw error in case of non string input
  264. if (typeof src === 'undefined' || src === null) {
  265. throw new Error('marked.parseInline(): input parameter is undefined or null');
  266. }
  267. if (typeof src !== 'string') {
  268. throw new Error('marked.parseInline(): input parameter is of type '
  269. + Object.prototype.toString.call(src) + ', string expected');
  270. }
  271. opt = merge({}, marked.defaults, opt || {});
  272. checkSanitizeDeprecation(opt);
  273. try {
  274. const tokens = Lexer.lexInline(src, opt);
  275. if (opt.walkTokens) {
  276. marked.walkTokens(tokens, opt.walkTokens);
  277. }
  278. return Parser.parseInline(tokens, opt);
  279. } catch (e) {
  280. e.message += '\nPlease report this to https://github.com/markedjs/marked.';
  281. if (opt.silent) {
  282. return '<p>An error occurred:</p><pre>'
  283. + escape(e.message + '', true)
  284. + '</pre>';
  285. }
  286. throw e;
  287. }
  288. };
  289. /**
  290. * Expose
  291. */
  292. marked.Parser = Parser;
  293. marked.parser = Parser.parse;
  294. marked.Renderer = Renderer;
  295. marked.TextRenderer = TextRenderer;
  296. marked.Lexer = Lexer;
  297. marked.lexer = Lexer.lex;
  298. marked.Tokenizer = Tokenizer;
  299. marked.Slugger = Slugger;
  300. marked.parse = marked;
  301. export const options = marked.options;
  302. export const setOptions = marked.setOptions;
  303. export const use = marked.use;
  304. export const walkTokens = marked.walkTokens;
  305. export const parseInline = marked.parseInline;
  306. export const parse = marked;
  307. export const parser = Parser.parse;
  308. export const lexer = Lexer.lex;
  309. export { defaults, getDefaults } from './defaults.js';
  310. export { Lexer } from './Lexer.js';
  311. export { Parser } from './Parser.js';
  312. export { Tokenizer } from './Tokenizer.js';
  313. export { Renderer } from './Renderer.js';
  314. export { TextRenderer } from './TextRenderer.js';
  315. export { Slugger } from './Slugger.js';