123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- // Main parser class
- 'use strict';
- var utils = require('./common/utils');
- var helpers = require('./helpers');
- var Renderer = require('./renderer');
- var ParserCore = require('./parser_core');
- var ParserBlock = require('./parser_block');
- var ParserInline = require('./parser_inline');
- var LinkifyIt = require('linkify-it');
- var mdurl = require('mdurl');
- var punycode = require('punycode');
- var config = {
- default: require('./presets/default'),
- zero: require('./presets/zero'),
- commonmark: require('./presets/commonmark')
- };
- ////////////////////////////////////////////////////////////////////////////////
- //
- // This validator can prohibit more than really needed to prevent XSS. It's a
- // tradeoff to keep code simple and to be secure by default.
- //
- // If you need different setup - override validator method as you wish. Or
- // replace it with dummy function and use external sanitizer.
- //
- var BAD_PROTO_RE = /^(vbscript|javascript|file|data):/;
- var GOOD_DATA_RE = /^data:image\/(gif|png|jpeg|webp);/;
- function validateLink(url) {
- // url should be normalized at this point, and existing entities are decoded
- var str = url.trim().toLowerCase();
- return BAD_PROTO_RE.test(str) ? (GOOD_DATA_RE.test(str) ? true : false) : true;
- }
- ////////////////////////////////////////////////////////////////////////////////
- var RECODE_HOSTNAME_FOR = [ 'http:', 'https:', 'mailto:' ];
- function normalizeLink(url) {
- var parsed = mdurl.parse(url, true);
- if (parsed.hostname) {
- // Encode hostnames in urls like:
- // `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
- //
- // We don't encode unknown schemas, because it's likely that we encode
- // something we shouldn't (e.g. `skype:name` treated as `skype:host`)
- //
- if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) {
- try {
- parsed.hostname = punycode.toASCII(parsed.hostname);
- } catch (er) { /**/ }
- }
- }
- return mdurl.encode(mdurl.format(parsed));
- }
- function normalizeLinkText(url) {
- var parsed = mdurl.parse(url, true);
- if (parsed.hostname) {
- // Encode hostnames in urls like:
- // `http://host/`, `https://host/`, `mailto:user@host`, `//host/`
- //
- // We don't encode unknown schemas, because it's likely that we encode
- // something we shouldn't (e.g. `skype:name` treated as `skype:host`)
- //
- if (!parsed.protocol || RECODE_HOSTNAME_FOR.indexOf(parsed.protocol) >= 0) {
- try {
- parsed.hostname = punycode.toUnicode(parsed.hostname);
- } catch (er) { /**/ }
- }
- }
- // add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720
- return mdurl.decode(mdurl.format(parsed), mdurl.decode.defaultChars + '%');
- }
- /**
- * class MarkdownIt
- *
- * Main parser/renderer class.
- *
- * ##### Usage
- *
- * ```javascript
- * // node.js, "classic" way:
- * var MarkdownIt = require('markdown-it'),
- * md = new MarkdownIt();
- * var result = md.render('# markdown-it rulezz!');
- *
- * // node.js, the same, but with sugar:
- * var md = require('markdown-it')();
- * var result = md.render('# markdown-it rulezz!');
- *
- * // browser without AMD, added to "window" on script load
- * // Note, there are no dash.
- * var md = window.markdownit();
- * var result = md.render('# markdown-it rulezz!');
- * ```
- *
- * Single line rendering, without paragraph wrap:
- *
- * ```javascript
- * var md = require('markdown-it')();
- * var result = md.renderInline('__markdown-it__ rulezz!');
- * ```
- **/
- /**
- * new MarkdownIt([presetName, options])
- * - presetName (String): optional, `commonmark` / `zero`
- * - options (Object)
- *
- * Creates parser instanse with given config. Can be called without `new`.
- *
- * ##### presetName
- *
- * MarkdownIt provides named presets as a convenience to quickly
- * enable/disable active syntax rules and options for common use cases.
- *
- * - ["commonmark"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/commonmark.js) -
- * configures parser to strict [CommonMark](http://commonmark.org/) mode.
- * - [default](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/default.js) -
- * similar to GFM, used when no preset name given. Enables all available rules,
- * but still without html, typographer & autolinker.
- * - ["zero"](https://github.com/markdown-it/markdown-it/blob/master/lib/presets/zero.js) -
- * all rules disabled. Useful to quickly setup your config via `.enable()`.
- * For example, when you need only `bold` and `italic` markup and nothing else.
- *
- * ##### options:
- *
- * - __html__ - `false`. Set `true` to enable HTML tags in source. Be careful!
- * That's not safe! You may need external sanitizer to protect output from XSS.
- * It's better to extend features via plugins, instead of enabling HTML.
- * - __xhtmlOut__ - `false`. Set `true` to add '/' when closing single tags
- * (`<br />`). This is needed only for full CommonMark compatibility. In real
- * world you will need HTML output.
- * - __breaks__ - `false`. Set `true` to convert `\n` in paragraphs into `<br>`.
- * - __langPrefix__ - `language-`. CSS language class prefix for fenced blocks.
- * Can be useful for external highlighters.
- * - __linkify__ - `false`. Set `true` to autoconvert URL-like text to links.
- * - __typographer__ - `false`. Set `true` to enable [some language-neutral
- * replacement](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js) +
- * quotes beautification (smartquotes).
- * - __quotes__ - `“”‘’`, String or Array. Double + single quotes replacement
- * pairs, when typographer enabled and smartquotes on. For example, you can
- * use `'«»„“'` for Russian, `'„“‚‘'` for German, and
- * `['«\xA0', '\xA0»', '‹\xA0', '\xA0›']` for French (including nbsp).
- * - __highlight__ - `null`. Highlighter function for fenced code blocks.
- * Highlighter `function (str, lang)` should return escaped HTML. It can also
- * return empty string if the source was not changed and should be escaped
- * externaly. If result starts with <pre... internal wrapper is skipped.
- *
- * ##### Example
- *
- * ```javascript
- * // commonmark mode
- * var md = require('markdown-it')('commonmark');
- *
- * // default mode
- * var md = require('markdown-it')();
- *
- * // enable everything
- * var md = require('markdown-it')({
- * html: true,
- * linkify: true,
- * typographer: true
- * });
- * ```
- *
- * ##### Syntax highlighting
- *
- * ```js
- * var hljs = require('highlight.js') // https://highlightjs.org/
- *
- * var md = require('markdown-it')({
- * highlight: function (str, lang) {
- * if (lang && hljs.getLanguage(lang)) {
- * try {
- * return hljs.highlight(str, { language: lang, ignoreIllegals: true }).value;
- * } catch (__) {}
- * }
- *
- * return ''; // use external default escaping
- * }
- * });
- * ```
- *
- * Or with full wrapper override (if you need assign class to `<pre>`):
- *
- * ```javascript
- * var hljs = require('highlight.js') // https://highlightjs.org/
- *
- * // Actual default values
- * var md = require('markdown-it')({
- * highlight: function (str, lang) {
- * if (lang && hljs.getLanguage(lang)) {
- * try {
- * return '<pre class="hljs"><code>' +
- * hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
- * '</code></pre>';
- * } catch (__) {}
- * }
- *
- * return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>';
- * }
- * });
- * ```
- *
- **/
- function MarkdownIt(presetName, options) {
- if (!(this instanceof MarkdownIt)) {
- return new MarkdownIt(presetName, options);
- }
- if (!options) {
- if (!utils.isString(presetName)) {
- options = presetName || {};
- presetName = 'default';
- }
- }
- /**
- * MarkdownIt#inline -> ParserInline
- *
- * Instance of [[ParserInline]]. You may need it to add new rules when
- * writing plugins. For simple rules control use [[MarkdownIt.disable]] and
- * [[MarkdownIt.enable]].
- **/
- this.inline = new ParserInline();
- /**
- * MarkdownIt#block -> ParserBlock
- *
- * Instance of [[ParserBlock]]. You may need it to add new rules when
- * writing plugins. For simple rules control use [[MarkdownIt.disable]] and
- * [[MarkdownIt.enable]].
- **/
- this.block = new ParserBlock();
- /**
- * MarkdownIt#core -> Core
- *
- * Instance of [[Core]] chain executor. You may need it to add new rules when
- * writing plugins. For simple rules control use [[MarkdownIt.disable]] and
- * [[MarkdownIt.enable]].
- **/
- this.core = new ParserCore();
- /**
- * MarkdownIt#renderer -> Renderer
- *
- * Instance of [[Renderer]]. Use it to modify output look. Or to add rendering
- * rules for new token types, generated by plugins.
- *
- * ##### Example
- *
- * ```javascript
- * var md = require('markdown-it')();
- *
- * function myToken(tokens, idx, options, env, self) {
- * //...
- * return result;
- * };
- *
- * md.renderer.rules['my_token'] = myToken
- * ```
- *
- * See [[Renderer]] docs and [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js).
- **/
- this.renderer = new Renderer();
- /**
- * MarkdownIt#linkify -> LinkifyIt
- *
- * [linkify-it](https://github.com/markdown-it/linkify-it) instance.
- * Used by [linkify](https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/linkify.js)
- * rule.
- **/
- this.linkify = new LinkifyIt();
- /**
- * MarkdownIt#validateLink(url) -> Boolean
- *
- * Link validation function. CommonMark allows too much in links. By default
- * we disable `javascript:`, `vbscript:`, `file:` schemas, and almost all `data:...` schemas
- * except some embedded image types.
- *
- * You can change this behaviour:
- *
- * ```javascript
- * var md = require('markdown-it')();
- * // enable everything
- * md.validateLink = function () { return true; }
- * ```
- **/
- this.validateLink = validateLink;
- /**
- * MarkdownIt#normalizeLink(url) -> String
- *
- * Function used to encode link url to a machine-readable format,
- * which includes url-encoding, punycode, etc.
- **/
- this.normalizeLink = normalizeLink;
- /**
- * MarkdownIt#normalizeLinkText(url) -> String
- *
- * Function used to decode link url to a human-readable format`
- **/
- this.normalizeLinkText = normalizeLinkText;
- // Expose utils & helpers for easy acces from plugins
- /**
- * MarkdownIt#utils -> utils
- *
- * Assorted utility functions, useful to write plugins. See details
- * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/common/utils.js).
- **/
- this.utils = utils;
- /**
- * MarkdownIt#helpers -> helpers
- *
- * Link components parser functions, useful to write plugins. See details
- * [here](https://github.com/markdown-it/markdown-it/blob/master/lib/helpers).
- **/
- this.helpers = utils.assign({}, helpers);
- this.options = {};
- this.configure(presetName);
- if (options) { this.set(options); }
- }
- /** chainable
- * MarkdownIt.set(options)
- *
- * Set parser options (in the same format as in constructor). Probably, you
- * will never need it, but you can change options after constructor call.
- *
- * ##### Example
- *
- * ```javascript
- * var md = require('markdown-it')()
- * .set({ html: true, breaks: true })
- * .set({ typographer, true });
- * ```
- *
- * __Note:__ To achieve the best possible performance, don't modify a
- * `markdown-it` instance options on the fly. If you need multiple configurations
- * it's best to create multiple instances and initialize each with separate
- * config.
- **/
- MarkdownIt.prototype.set = function (options) {
- utils.assign(this.options, options);
- return this;
- };
- /** chainable, internal
- * MarkdownIt.configure(presets)
- *
- * Batch load of all options and compenent settings. This is internal method,
- * and you probably will not need it. But if you will - see available presets
- * and data structure [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets)
- *
- * We strongly recommend to use presets instead of direct config loads. That
- * will give better compatibility with next versions.
- **/
- MarkdownIt.prototype.configure = function (presets) {
- var self = this, presetName;
- if (utils.isString(presets)) {
- presetName = presets;
- presets = config[presetName];
- if (!presets) { throw new Error('Wrong `markdown-it` preset "' + presetName + '", check name'); }
- }
- if (!presets) { throw new Error('Wrong `markdown-it` preset, can\'t be empty'); }
- if (presets.options) { self.set(presets.options); }
- if (presets.components) {
- Object.keys(presets.components).forEach(function (name) {
- if (presets.components[name].rules) {
- self[name].ruler.enableOnly(presets.components[name].rules);
- }
- if (presets.components[name].rules2) {
- self[name].ruler2.enableOnly(presets.components[name].rules2);
- }
- });
- }
- return this;
- };
- /** chainable
- * MarkdownIt.enable(list, ignoreInvalid)
- * - list (String|Array): rule name or list of rule names to enable
- * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.
- *
- * Enable list or rules. It will automatically find appropriate components,
- * containing rules with given names. If rule not found, and `ignoreInvalid`
- * not set - throws exception.
- *
- * ##### Example
- *
- * ```javascript
- * var md = require('markdown-it')()
- * .enable(['sub', 'sup'])
- * .disable('smartquotes');
- * ```
- **/
- MarkdownIt.prototype.enable = function (list, ignoreInvalid) {
- var result = [];
- if (!Array.isArray(list)) { list = [ list ]; }
- [ 'core', 'block', 'inline' ].forEach(function (chain) {
- result = result.concat(this[chain].ruler.enable(list, true));
- }, this);
- result = result.concat(this.inline.ruler2.enable(list, true));
- var missed = list.filter(function (name) { return result.indexOf(name) < 0; });
- if (missed.length && !ignoreInvalid) {
- throw new Error('MarkdownIt. Failed to enable unknown rule(s): ' + missed);
- }
- return this;
- };
- /** chainable
- * MarkdownIt.disable(list, ignoreInvalid)
- * - list (String|Array): rule name or list of rule names to disable.
- * - ignoreInvalid (Boolean): set `true` to ignore errors when rule not found.
- *
- * The same as [[MarkdownIt.enable]], but turn specified rules off.
- **/
- MarkdownIt.prototype.disable = function (list, ignoreInvalid) {
- var result = [];
- if (!Array.isArray(list)) { list = [ list ]; }
- [ 'core', 'block', 'inline' ].forEach(function (chain) {
- result = result.concat(this[chain].ruler.disable(list, true));
- }, this);
- result = result.concat(this.inline.ruler2.disable(list, true));
- var missed = list.filter(function (name) { return result.indexOf(name) < 0; });
- if (missed.length && !ignoreInvalid) {
- throw new Error('MarkdownIt. Failed to disable unknown rule(s): ' + missed);
- }
- return this;
- };
- /** chainable
- * MarkdownIt.use(plugin, params)
- *
- * Load specified plugin with given params into current parser instance.
- * It's just a sugar to call `plugin(md, params)` with curring.
- *
- * ##### Example
- *
- * ```javascript
- * var iterator = require('markdown-it-for-inline');
- * var md = require('markdown-it')()
- * .use(iterator, 'foo_replace', 'text', function (tokens, idx) {
- * tokens[idx].content = tokens[idx].content.replace(/foo/g, 'bar');
- * });
- * ```
- **/
- MarkdownIt.prototype.use = function (plugin /*, params, ... */) {
- var args = [ this ].concat(Array.prototype.slice.call(arguments, 1));
- plugin.apply(plugin, args);
- return this;
- };
- /** internal
- * MarkdownIt.parse(src, env) -> Array
- * - src (String): source string
- * - env (Object): environment sandbox
- *
- * Parse input string and return list of block tokens (special token type
- * "inline" will contain list of inline tokens). You should not call this
- * method directly, until you write custom renderer (for example, to produce
- * AST).
- *
- * `env` is used to pass data between "distributed" rules and return additional
- * metadata like reference info, needed for the renderer. It also can be used to
- * inject data in specific cases. Usually, you will be ok to pass `{}`,
- * and then pass updated object to renderer.
- **/
- MarkdownIt.prototype.parse = function (src, env) {
- if (typeof src !== 'string') {
- throw new Error('Input data should be a String');
- }
- var state = new this.core.State(src, this, env);
- this.core.process(state);
- return state.tokens;
- };
- /**
- * MarkdownIt.render(src [, env]) -> String
- * - src (String): source string
- * - env (Object): environment sandbox
- *
- * Render markdown string into html. It does all magic for you :).
- *
- * `env` can be used to inject additional metadata (`{}` by default).
- * But you will not need it with high probability. See also comment
- * in [[MarkdownIt.parse]].
- **/
- MarkdownIt.prototype.render = function (src, env) {
- env = env || {};
- return this.renderer.render(this.parse(src, env), this.options, env);
- };
- /** internal
- * MarkdownIt.parseInline(src, env) -> Array
- * - src (String): source string
- * - env (Object): environment sandbox
- *
- * The same as [[MarkdownIt.parse]] but skip all block rules. It returns the
- * block tokens list with the single `inline` element, containing parsed inline
- * tokens in `children` property. Also updates `env` object.
- **/
- MarkdownIt.prototype.parseInline = function (src, env) {
- var state = new this.core.State(src, this, env);
- state.inlineMode = true;
- this.core.process(state);
- return state.tokens;
- };
- /**
- * MarkdownIt.renderInline(src [, env]) -> String
- * - src (String): source string
- * - env (Object): environment sandbox
- *
- * Similar to [[MarkdownIt.render]] but for single paragraph content. Result
- * will NOT be wrapped into `<p>` tags.
- **/
- MarkdownIt.prototype.renderInline = function (src, env) {
- env = env || {};
- return this.renderer.render(this.parseInline(src, env), this.options, env);
- };
- module.exports = MarkdownIt;
|