UNPKG

markdown-it

Version:

Markdown-it - modern pluggable markdown parser.

1,621 lines 118 kB
/*! markdown-it 15.0.0 https://github.com/markdown-it/markdown-it @license MIT */ import * as mdurl from "mdurl"; import * as ucmicro from "uc.micro"; import { decodeHTMLStrict } from "entities"; import { LinkifyIt } from "linkify-it"; import punycode from "punycode.js"; //#region \0rolldown/runtime.js var __defProp = Object.defineProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); return target; }; //#endregion //#region src/common/utils.ts /** * Common utility functions exposed through `md.utils` for use by plugins. * * @module md.utils */ var utils_exports = /* @__PURE__ */ __exportAll({ arrayReplaceAt: () => arrayReplaceAt, asciiTrim: () => asciiTrim, callable: () => callable, escapeHtml: () => escapeHtml, escapeRE: () => escapeRE, fromCodePoint: () => fromCodePoint, isMdAsciiPunct: () => isMdAsciiPunct, isPunctChar: () => isPunctChar, isPunctCharCode: () => isPunctCharCode, isSpace: () => isSpace, isValidEntityCode: () => isValidEntityCode, isWhiteSpace: () => isWhiteSpace, lib: () => lib, normalizeReference: () => normalizeReference, unescapeAll: () => unescapeAll, unescapeMd: () => unescapeMd }); function callable(cls) { const wrapper = function(...args) { return Reflect.construct(cls, args, new.target && new.target !== wrapper ? new.target : cls); }; Object.defineProperty(wrapper, "name", { value: cls.name }); Object.setPrototypeOf(wrapper, cls); wrapper.prototype = cls.prototype; return wrapper; } /** * Returns a copy of a token array with the token at `pos` replaced by * `newElements`. Used to transform token streams without modifying the * original array. */ function arrayReplaceAt(src, pos, newElements) { return [].concat(src.slice(0, pos), newElements, src.slice(pos + 1)); } /** Checks whether a code point can be decoded from a numeric HTML entity. */ function isValidEntityCode(c) { if (c >= 55296 && c <= 57343) return false; if (c >= 64976 && c <= 65007) return false; if ((c & 65535) === 65535 || (c & 65535) === 65534) return false; if (c >= 0 && c <= 8) return false; if (c === 11) return false; if (c >= 14 && c <= 31) return false; if (c >= 127 && c <= 159) return false; if (c > 1114111) return false; return true; } /** * Converts a Unicode code point to a string, like `String.fromCodePoint()`, * but does not throw for invalid input. */ function fromCodePoint(c) { if (c > 65535) { c -= 65536; const surrogate1 = 55296 + (c >> 10); const surrogate2 = 56320 + (c & 1023); return String.fromCharCode(surrogate1, surrogate2); } return String.fromCharCode(c); } var UNESCAPE_MD_RE = /\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g; var UNESCAPE_ALL_RE = new RegExp(`${UNESCAPE_MD_RE.source}|${/&([a-z#][a-z0-9]{1,31});/gi.source}`, "gi"); var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))$/i; function replaceEntityPattern(match, name) { if (name.charCodeAt(0) === 35 && DIGITAL_ENTITY_TEST_RE.test(name)) { const code = name[1].toLowerCase() === "x" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10); if (isValidEntityCode(code)) return fromCodePoint(code); return match; } const decoded = decodeHTMLStrict(match); if (decoded !== match) return decoded; return match; } /** Decodes Markdown backslash escapes. */ function unescapeMd(str) { if (str.indexOf("\\") < 0) return str; return str.replace(UNESCAPE_MD_RE, "$1"); } /** * Decodes Markdown backslash escapes and HTML character references in link * destinations, link titles, and fenced code info strings. */ function unescapeAll(str) { if (str.indexOf("\\") < 0 && str.indexOf("&") < 0) return str; return str.replace(UNESCAPE_ALL_RE, function(match, escaped, entity) { if (escaped) return escaped; return replaceEntityPattern(match, entity); }); } var HTML_ESCAPE_TEST_RE = /[&<>"]/; var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g; var HTML_REPLACEMENTS = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;" }; function replaceUnsafeChar(ch) { return HTML_REPLACEMENTS[ch]; } /** Escapes HTML special characters in a string. */ function escapeHtml(str) { if (HTML_ESCAPE_TEST_RE.test(str)) return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar); return str; } var REGEXP_ESCAPE_RE = /[.?*+^$[\]\\(){}|-]/g; /** Escapes regular expression metacharacters in a string. */ function escapeRE(str) { return str.replace(REGEXP_ESCAPE_RE, "\\$&"); } /** Checks whether a character code is an ASCII space or tab. */ function isSpace(code) { switch (code) { case 9: case 32: return true; } return false; } /** * Checks whether a character code is whitespace recognized by Markdown. * * Matches the Unicode `Zs` category or `\t`, `\f`, `\v`, `\r`, `\n`. */ function isWhiteSpace(code) { if (code >= 8192 && code <= 8202) return true; switch (code) { case 9: case 10: case 11: case 12: case 13: case 32: case 160: case 5760: case 8239: case 8287: case 12288: return true; } return false; } /** * Checks whether a character is Unicode punctuation or a symbol. * * Does not support astral characters. */ function isPunctChar(ch) { return ucmicro.P.test(ch) || ucmicro.S.test(ch); } /** Checks whether a Unicode code point is punctuation or a symbol. */ function isPunctCharCode(code) { return isPunctChar(fromCodePoint(code)); } /** * Markdown ASCII punctuation characters. * * !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, * [, \, ], ^, _, `, {, |, }, or ~ * * http://spec.commonmark.org/0.15/#ascii-punctuation-character * * Don't confuse with Unicode punctuation. It lacks some characters in the * ASCII range. */ function isMdAsciiPunct(ch) { switch (ch) { case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 58: case 59: case 60: case 61: case 62: case 63: case 64: case 91: case 92: case 93: case 94: case 95: case 96: case 123: case 124: case 125: case 126: return true; default: return false; } } /** Normalizes `[reference labels]` for case-insensitive lookup. */ function normalizeReference(str) { str = str.trim().replace(/\s+/g, " "); return str.toLowerCase().toUpperCase(); } function isAsciiTrimmable(c) { return c === 32 || c === 9 || c === 10 || c === 13; } /** * "Light" `.trim()` for blocks (headings, paragraphs), where Unicode spaces * should be preserved. */ function asciiTrim(str) { let start = 0; for (; start < str.length; start++) if (!isAsciiTrimmable(str.charCodeAt(start))) break; let end = str.length - 1; for (; end >= start; end--) if (!isAsciiTrimmable(str.charCodeAt(end))) break; return str.slice(start, end + 1); } /** * Libraries commonly used by markdown-it and its plugins, re-exported to * reduce duplicate dependencies in browser bundles. */ var lib = { mdurl, ucmicro }; //#endregion //#region src/helpers/parse_link_label.ts /** Finds the end of a link or image label (`[label]`). */ function parseLinkLabel(state, start, disableNested) { let level, found, marker, prevPos; const max = state.posMax; const oldPos = state.pos; state.pos = start + 1; level = 1; while (state.pos < max) { marker = state.src.charCodeAt(state.pos); if (marker === 93) { level--; if (level === 0) { found = true; break; } } prevPos = state.pos; state.md.inline.skipToken(state); if (marker === 91) { if (prevPos === state.pos - 1) level++; else if (disableNested) { state.pos = oldPos; return -1; } } } let labelEnd = -1; if (found) labelEnd = state.pos; state.pos = oldPos; return labelEnd; } //#endregion //#region src/helpers/parse_link_destination.ts /** Parses the destination in `[label](destination "title")`. */ function parseLinkDestination(str, start, max) { let code; let pos = start; const result = { ok: false, pos: 0, str: "" }; if (str.charCodeAt(pos) === 60) { pos++; while (pos < max) { code = str.charCodeAt(pos); if (code === 10) return result; if (code === 60) return result; if (code === 62) { result.pos = pos + 1; result.str = unescapeAll(str.slice(start + 1, pos)); result.ok = true; return result; } if (code === 92 && pos + 1 < max) { pos += 2; continue; } pos++; } return result; } let level = 0; while (pos < max) { code = str.charCodeAt(pos); if (code === 32) break; if (code < 32 || code === 127) break; if (code === 92 && pos + 1 < max) { if (str.charCodeAt(pos + 1) === 32) { pos++; continue; } pos += 2; continue; } if (code === 40) { level++; if (level > 32) return result; } if (code === 41) { if (level === 0) break; level--; } pos++; } if (start === pos) return result; if (level !== 0) return result; result.str = unescapeAll(str.slice(start, pos)); result.pos = pos; result.ok = true; return result; } //#endregion //#region src/helpers/parse_link_title.ts /** * Parses the optional title in `[label](destination "title")` or * `[label]: destination "title"`. * * `prev_state` continues a reference title on the next source line. */ function parseLinkTitle(str, start, max, prev_state) { let code; let pos = start; const state = { ok: false, can_continue: false, pos: 0, str: "", marker: 0 }; if (prev_state) { state.str = prev_state.str; state.marker = prev_state.marker; } else { if (pos >= max) return state; let marker = str.charCodeAt(pos); if (marker !== 34 && marker !== 39 && marker !== 40) return state; start++; pos++; if (marker === 40) marker = 41; state.marker = marker; } while (pos < max) { code = str.charCodeAt(pos); if (code === state.marker) { state.pos = pos + 1; state.str += unescapeAll(str.slice(start, pos)); state.ok = true; return state; } else if (code === 40 && state.marker === 41) return state; else if (code === 92 && pos + 1 < max) pos++; pos++; } state.can_continue = true; state.str += unescapeAll(str.slice(start, pos)); return state; } //#endregion //#region src/helpers/index.ts /** * Functions used to parse links and images, split out of parser rules because * of their size. * * @module md.helpers */ var helpers_exports = /* @__PURE__ */ __exportAll({ parseLinkDestination: () => parseLinkDestination, parseLinkLabel: () => parseLinkLabel, parseLinkTitle: () => parseLinkTitle }); //#endregion //#region \0@oxc-project+runtime@0.142.0/helpers/esm/typeof.js function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) { return typeof o; } : function(o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } //#endregion //#region \0@oxc-project+runtime@0.142.0/helpers/esm/toPrimitive.js function toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } //#endregion //#region \0@oxc-project+runtime@0.142.0/helpers/esm/toPropertyKey.js function toPropertyKey(t) { var i = toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } //#endregion //#region \0@oxc-project+runtime@0.142.0/helpers/esm/defineProperty.js function _defineProperty(e, r, t) { return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } //#endregion //#region src/token.ts /** * Represents one item in the parsed token stream, storing parsed data and * providing helpers for managing HTML attributes. */ var Token = class { constructor(type, tag, nesting) { _defineProperty( this, /** * Source map info. Format: `[ line_begin, line_end ]` */ "map", null ); _defineProperty( this, /** * nesting level, the same as `state.level` */ "level", 0 ); _defineProperty( this, /** * An array of child nodes (inline and img tokens) */ "children", null ); _defineProperty( this, /** * In a case of self-closing tag (code, html, fence, etc.), * it has contents of this tag. */ "content", "" ); _defineProperty( this, /** * '*' or '_' for emphasis, fence string for fence, etc. */ "markup", "" ); _defineProperty( this, /** * Additional information: * * - Info string for "fence" tokens * - The value "auto" for autolink "link_open" and "link_close" tokens * - The string value of the item marker for ordered-list "list_item_open" tokens */ "info", "" ); _defineProperty( this, /** * True for block-level tokens, false for inline tokens. * Used in renderer to calculate line breaks */ "block", false ); _defineProperty( this, /** * If it's true, ignore this element when rendering. Used for tight lists * to hide paragraphs. */ "hidden", false ); this.type = type; this.tag = tag; this.attrs = null; this.nesting = nesting; this.meta = null; } /** * Search attribute index by name. */ attrIndex(name) { if (!this.attrs) return -1; const attrs = this.attrs; for (let i = 0, len = attrs.length; i < len; i++) if (attrs[i][0] === name) return i; return -1; } /** * Add `[ name, value ]` attribute to list. Init attrs if necessary */ attrPush(attrData) { if (this.attrs) this.attrs.push(attrData); else this.attrs = [attrData]; } /** * Set `name` attribute to `value`. Override old value if exists. */ attrSet(name, value) { const idx = this.attrIndex(name); const attrData = [name, value]; if (idx < 0) this.attrPush(attrData); else this.attrs[idx] = attrData; } /** * Get the value of attribute `name`, or null if it does not exist. */ attrGet(name) { const idx = this.attrIndex(name); let value = null; if (idx >= 0) value = this.attrs[idx][1]; return value; } /** * Join value to existing attribute via space. Or create new attribute if not * exists. Useful to operate with token classes. */ attrJoin(name, value) { const idx = this.attrIndex(name); if (idx < 0) this.attrPush([name, value]); else this.attrs[idx][1] = `${this.attrs[idx][1]} ${value}`; } }; //#endregion //#region src/ruler.ts /** * Helper class, used by {@link MarkdownIt.core}, {@link MarkdownIt.block} and * {@link MarkdownIt.inline} to manage sequences of functions (rules): * * - keep rules in defined order * - assign the name to each rule * - enable/disable rules * - add/replace rules * - allow assign rules to additional named chains (in the same) * - cacheing lists of active rules * * You will not need use this class directly until write plugins. For simple * rules control use {@link MarkdownIt.disable}, {@link MarkdownIt.enable} and * {@link MarkdownIt.use}. */ var Ruler = class { constructor() { _defineProperty(this, "__rules__", []); _defineProperty(this, "__cache__", null); } __find__(name) { for (let i = 0; i < this.__rules__.length; i++) if (this.__rules__[i].name === name) return i; return -1; } __compile__() { const chains = /* @__PURE__ */ new Set(); this.__rules__.forEach((rule) => { if (!rule.enabled) return; rule.alt.forEach((altName) => { if (altName) chains.add(altName); }); }); this.__cache__ = Object.create(null); this.__cache__[""] = []; this.__rules__.forEach((rule) => { if (rule.enabled) this.__cache__[""].push(rule.fn); }); chains.forEach((chain) => { this.__cache__[chain] = []; this.__rules__.forEach((rule) => { if (rule.enabled && rule.alt.indexOf(chain) >= 0) this.__cache__[chain].push(rule.fn); }); }); } /** * Replace rule by name with new function & options. Throws error if name not * found. * * @param name Rule name to replace. * @param fn New rule function. * @param options Rule options. `alt` is an array with names of "alternate" * chains. * * @example Replace existing typographer replacement rule with new one * ```javascript * import MarkdownIt from 'markdown-it' * const md = new MarkdownIt() * * md.core.ruler.at('replacements', function replace(state) { * //... * }); * ``` */ at(name, fn, options = {}) { const index = this.__find__(name); if (index === -1) throw new Error(`Parser rule not found: ${name}`); this.__rules__[index].fn = fn; this.__rules__[index].alt = options.alt || []; this.__cache__ = null; } /** * Add new rule to chain before one with given name. See also * {@link Ruler.after}, {@link Ruler.push}. * * @param beforeName New rule will be added before this one. * @param ruleName Name of added rule. * @param fn Rule function. * @param options Rule options. `alt` is an array with names of "alternate" * chains. * * @example * ```javascript * import MarkdownIt from 'markdown-it' * const md = new MarkdownIt() * * md.block.ruler.before('paragraph', 'my_rule', function replace(state) { * //... * }); * ``` */ before(beforeName, ruleName, fn, options = {}) { const index = this.__find__(beforeName); if (index === -1) throw new Error(`Parser rule not found: ${beforeName}`); this.__rules__.splice(index, 0, { name: ruleName, enabled: true, fn, alt: options.alt || [] }); this.__cache__ = null; } /** * Add new rule to chain after one with given name. See also * {@link Ruler.before}, {@link Ruler.push}. * * @param afterName New rule will be added after this one. * @param ruleName Name of added rule. * @param fn Rule function. * @param options Rule options. `alt` is an array with names of "alternate" * chains. * * @example * ```javascript * import MarkdownIt from 'markdown-it' * const md = new MarkdownIt() * * md.inline.ruler.after('text', 'my_rule', function replace(state) { * //... * }); * ``` */ after(afterName, ruleName, fn, options = {}) { const index = this.__find__(afterName); if (index === -1) throw new Error(`Parser rule not found: ${afterName}`); this.__rules__.splice(index + 1, 0, { name: ruleName, enabled: true, fn, alt: options.alt || [] }); this.__cache__ = null; } /** * Push new rule to the end of chain. See also * {@link Ruler.before}, {@link Ruler.after}. * * @param ruleName Name of added rule. * @param fn Rule function. * @param options Rule options. `alt` is an array with names of "alternate" * chains. * * @example * ```javascript * import MarkdownIt from 'markdown-it' * const md = new MarkdownIt() * * md.core.ruler.push('my_rule', function replace(state) { * //... * }); * ``` */ push(ruleName, fn, options = {}) { this.__rules__.push({ name: ruleName, enabled: true, fn, alt: options.alt || [] }); this.__cache__ = null; } /** * Enable rules with given names. If any rule name not found - throw Error. * Errors can be disabled by second param. * * See also {@link Ruler.disable}, {@link Ruler.enableOnly}. * * @param list List of rule names to enable. * @param ignoreInvalid Set `true` to ignore errors when rule not found. * @returns List of found rule names (if no exception happened). */ enable(list, ignoreInvalid = false) { if (!Array.isArray(list)) list = [list]; const result = []; list.forEach((name) => { const idx = this.__find__(name); if (idx < 0) { if (ignoreInvalid) return; throw new Error(`Rules manager: invalid rule name ${name}`); } this.__rules__[idx].enabled = true; result.push(name); }); this.__cache__ = null; return result; } /** * Enable rules with given names, and disable everything else. If any rule name * not found - throw Error. Errors can be disabled by second param. * * See also {@link Ruler.disable}, {@link Ruler.enable}. * * @param list List of rule names to enable (whitelist). * @param ignoreInvalid Set `true` to ignore errors when rule not found. */ enableOnly(list, ignoreInvalid = false) { if (!Array.isArray(list)) list = [list]; this.__rules__.forEach((rule) => { rule.enabled = false; }); this.enable(list, ignoreInvalid); } /** * Disable rules with given names. If any rule name not found - throw Error. * Errors can be disabled by second param. * * See also {@link Ruler.enable}, {@link Ruler.enableOnly}. * * @param list List of rule names to disable. * @param ignoreInvalid Set `true` to ignore errors when rule not found. * @returns List of found rule names (if no exception happened). */ disable(list, ignoreInvalid = false) { if (!Array.isArray(list)) list = [list]; const result = []; list.forEach((name) => { const idx = this.__find__(name); if (idx < 0) { if (ignoreInvalid) return; throw new Error(`Rules manager: invalid rule name ${name}`); } this.__rules__[idx].enabled = false; result.push(name); }); this.__cache__ = null; return result; } /** * Return array of active functions (rules) for given chain name. It analyzes * rules configuration, compiles caches if not exists and returns result. * * Default chain name is `''` (empty string). It can't be skipped. That's * done intentionally, to keep signature monomorphic for high speed. */ getRules(chainName) { if (!this.__cache__) this.__compile__(); return this.__cache__[chainName] || []; } }; //#endregion //#region src/renderer.ts var 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 (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][0], tmpAttrs[i][1]]; tmpAttrs[i][1] += ` ${options.langPrefix}${langName}`; } 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]; 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) { return options.xhtmlOut ? "<br />\n" : "<br>\n"; }; default_rules.softbreak = function(tokens, idx, options) { return options.breaks ? options.xhtmlOut ? "<br />\n" : "<br>\n" : "\n"; }; default_rules.text = function(tokens, idx) { return escapeHtml(tokens[idx].content); }; default_rules.html_block = function(tokens, idx) { return tokens[idx].content; }; default_rules.html_inline = function(tokens, idx) { return tokens[idx].content; }; /** * 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. * * Creates new renderer instance and fills {@link Renderer.rules} with defaults. */ var Renderer = class { constructor() { _defineProperty( this, /** * Contains render rules for tokens. Can be updated and extended. * * See [source code](https://github.com/markdown-it/markdown-it/blob/master/src/renderer.ts) * for more details and examples. * * @example Custom render rules * ```javascript * import MarkdownIt from 'markdown-it' * const md = new MarkdownIt() * * md.renderer.rules.strong_open = function () { return '<b>'; }; * md.renderer.rules.strong_close = function () { return '</b>'; }; * * const result = md.renderInline(...); * ``` * * @example Each rule is called as independent static function with fixed signature * ```javascript * function my_token_render(tokens, idx, options, env, renderer) { * // ... * return renderedHTML; * } * ``` */ "rules", Object.assign({}, default_rules) ); } /** * Render token attributes to string. */ 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(String(token.attrs[i][1]))}"`; return result; } /** * Default token renderer. Can be overriden by custom function * in {@link Renderer.rules}. * * @param tokens List of tokens. * @param idx Token index to render. * @param options Params of parser instance. */ renderToken(tokens, idx, options) { const token = tokens[idx]; let result = ""; if (token.hidden) return ""; let prev = idx - 1; while (prev >= 0 && tokens[prev].hidden && tokens[prev].nesting === 0) prev--; if (token.block && token.nesting !== -1 && prev >= 0 && tokens[prev].hidden && tokens[prev].nesting === -1) result += "\n"; result += (token.nesting === -1 ? "</" : "<") + token.tag; result += this.renderAttrs(token); if (token.nesting === 0 && options.xhtmlOut) result += " /"; let needLf = false; if (token.block) { needLf = true; if (token.nesting === 1) { let next = idx + 1; while (next < tokens.length && tokens[next].hidden && tokens[next].nesting === 0) next++; if (next < tokens.length) { const nextToken = tokens[next]; if (nextToken.type === "inline" || nextToken.hidden) needLf = false; else if (nextToken.nesting === -1 && nextToken.tag === token.tag) needLf = false; } } } result += needLf ? ">\n" : ">"; return result; } /** * The same as {@link Renderer.render}, but for single token of `inline` type. * * @param tokens List on block tokens to render. * @param options Params of parser instance. * @param env Additional data from parsed input (references, for example). */ renderInline(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; } /** * 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. * * @param tokens List on block tokens to render. * @param options Params of parser instance. * @param env Additional data from parsed input (references, for example). */ renderInlineAsText(tokens, options, env) { let result = ""; for (let i = 0, len = tokens.length; i < len; i++) switch (tokens[i].type) { case "text": case "code_inline": 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"; } return result; } /** * Takes token stream and generates HTML. Probably, you will never need to call * this method directly. * * @param tokens List on block tokens to render. * @param options Params of parser instance. * @param env Additional data from parsed input (references, for example). */ render(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); } return result; } }; //#endregion //#region src/rules_core/state_core.ts /** Mutable state passed through the core rules chain. */ var StateCore = class { constructor(src, md, env) { _defineProperty(this, "tokens", []); _defineProperty(this, "inlineMode", false); _defineProperty(this, "Token", Token); this.src = src; this.env = env; this.md = md; } }; //#endregion //#region src/rules_core/normalize.ts var NEWLINES_RE = /\r\n?|\n/g; var NULL_RE = /\0/g; function normalize(state) { let str; str = state.src.replace(NEWLINES_RE, "\n"); str = str.replace(NULL_RE, "�"); state.src = str; } //#endregion //#region src/rules_core/block.ts function block(state) { let token; if (state.inlineMode) { token = new state.Token("inline", "", 0); token.content = state.src; token.map = [0, 1]; token.children = []; state.tokens.push(token); } else state.md.block.parse(state.src, state.md, state.env, state.tokens); } //#endregion //#region src/rules_core/strip_references.ts function strip_references(state) { const tokens = state.tokens; let last = 0; for (let curr = 0; curr < tokens.length; curr++) { if (tokens[curr].type === "reference_definition") continue; if (curr !== last) tokens[last] = tokens[curr]; last++; } if (tokens.length !== last) tokens.length = last; } //#endregion //#region src/rules_core/inline.ts function inline(state) { const tokens = state.tokens; for (let i = 0, l = tokens.length; i < l; i++) { const tok = tokens[i]; if (tok.type === "inline") state.md.inline.parse(tok.content, state.md, state.env, tok.children); } } //#endregion //#region src/rules_core/linkify.ts function isLinkOpen$1(str) { return /^<a[>\s]/i.test(str); } function isLinkClose$1(str) { return /^<\/a\s*>/i.test(str); } function linkify$1(state) { const blockTokens = state.tokens; if (!state.md.options.linkify) return; for (let j = 0, l = blockTokens.length; j < l; j++) { if (blockTokens[j].type !== "inline" || !state.md.linkify.test(blockTokens[j].content)) continue; let tokens = blockTokens[j].children; let htmlLinkLevel = 0; for (let i = tokens.length - 1; i >= 0; i--) { const currentToken = tokens[i]; if (currentToken.type === "link_close") { i--; while (tokens[i].level !== currentToken.level && tokens[i].type !== "link_open") i--; continue; } if (currentToken.type === "html_inline") { if (isLinkOpen$1(currentToken.content) && htmlLinkLevel > 0) htmlLinkLevel--; if (isLinkClose$1(currentToken.content)) htmlLinkLevel++; } if (htmlLinkLevel > 0) continue; if (currentToken.type === "text" && state.md.linkify.test(currentToken.content)) { const text = currentToken.content; let links = state.md.linkify.match(text); const nodes = []; let level = currentToken.level; let lastPos = 0; if (links.length > 0 && links[0].index === 0 && i > 0 && tokens[i - 1].type === "text_special") links = links.slice(1); for (let ln = 0; ln < links.length; ln++) { const url = links[ln].url; const fullUrl = state.md.normalizeLink(url); if (!state.md.validateLink(fullUrl)) continue; let urlText = links[ln].text; if (!links[ln].schema) urlText = state.md.normalizeLinkText(`http://${urlText}`).replace(/^http:\/\//, ""); else if (links[ln].schema === "mailto:" && !/^mailto:/i.test(urlText)) urlText = state.md.normalizeLinkText(`mailto:${urlText}`).replace(/^mailto:/, ""); else urlText = state.md.normalizeLinkText(urlText); const pos = links[ln].index; if (pos > lastPos) { const token = new state.Token("text", "", 0); token.content = text.slice(lastPos, pos); token.level = level; nodes.push(token); } const token_o = new state.Token("link_open", "a", 1); token_o.attrs = [["href", fullUrl]]; token_o.level = level++; token_o.markup = "linkify"; token_o.info = "auto"; nodes.push(token_o); const token_t = new state.Token("text", "", 0); token_t.content = urlText; token_t.level = level; nodes.push(token_t); const token_c = new state.Token("link_close", "a", -1); token_c.level = --level; token_c.markup = "linkify"; token_c.info = "auto"; nodes.push(token_c); lastPos = links[ln].lastIndex; } if (lastPos < text.length) { const token = new state.Token("text", "", 0); token.content = text.slice(lastPos); token.level = level; nodes.push(token); } blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes); } } } } //#endregion //#region src/rules_core/replacements.ts var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/; var SCOPED_ABBR_TEST_RE = /\((c|tm|r)\)/i; var SCOPED_ABBR_RE = /\((c|tm|r)\)/gi; var SCOPED_ABBR = { c: "©", r: "®", tm: "™" }; function replaceFn(match, name) { return SCOPED_ABBR[name.toLowerCase()]; } function replace_scoped(inlineTokens) { let inside_autolink = 0; for (let i = inlineTokens.length - 1; i >= 0; i--) { const token = inlineTokens[i]; if (token.type === "text" && !inside_autolink) token.content = token.content.replace(SCOPED_ABBR_RE, replaceFn); if (token.type === "link_open" && token.info === "auto") inside_autolink--; if (token.type === "link_close" && token.info === "auto") inside_autolink++; } } function replace_rare(inlineTokens) { let inside_autolink = 0; for (let i = inlineTokens.length - 1; i >= 0; i--) { const token = inlineTokens[i]; if (token.type === "text" && !inside_autolink) { if (RARE_RE.test(token.content)) token.content = token.content.replace(/\+-/g, "±").replace(/\.{2,}/g, "…").replace(/([?!])…/g, "$1..").replace(/([?!]){4,}/g, "$1$1$1").replace(/,{2,}/g, ",").replace(/(^|[^-])---(?=[^-]|$)/gm, "$1—").replace(/(^|\s)--(?=\s|$)/gm, "$1–").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm, "$1–"); } if (token.type === "link_open" && token.info === "auto") inside_autolink--; if (token.type === "link_close" && token.info === "auto") inside_autolink++; } } function replace(state) { let blkIdx; if (!state.md.options.typographer) return; for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { if (state.tokens[blkIdx].type !== "inline") continue; if (SCOPED_ABBR_TEST_RE.test(state.tokens[blkIdx].content)) replace_scoped(state.tokens[blkIdx].children); if (RARE_RE.test(state.tokens[blkIdx].content)) replace_rare(state.tokens[blkIdx].children); } } //#endregion //#region src/rules_core/smartquotes.ts var QUOTE_TEST_RE = /['"]/; var QUOTE_RE = /['"]/g; var APOSTROPHE = "’"; function addReplacement(replacements, tokenIdx, pos, ch) { if (!replacements[tokenIdx]) replacements[tokenIdx] = []; replacements[tokenIdx].push({ pos, ch }); } function applyReplacements(str, replacements) { let result = ""; let lastPos = 0; replacements.sort((a, b) => a.pos - b.pos); for (let i = 0; i < replacements.length; i++) { const replacement = replacements[i]; result += str.slice(lastPos, replacement.pos) + replacement.ch; lastPos = replacement.pos + 1; } return result + str.slice(lastPos); } function process_inlines(tokens, state) { let j; const stack = []; const replacements = {}; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; const thisLevel = tokens[i].level; for (j = stack.length - 1; j >= 0; j--) if (stack[j].level <= thisLevel) break; stack.length = j + 1; if (token.type !== "text") continue; const text = token.content; let pos = 0; const max = text.length; OUTER: while (pos < max) { QUOTE_RE.lastIndex = pos; const t = QUOTE_RE.exec(text); if (!t) break; let canOpen = true; let canClose = true; pos = t.index + 1; const isSingle = t[0] === "'"; let lastChar = 32; if (t.index - 1 >= 0) lastChar = text.charCodeAt(t.index - 1); else for (j = i - 1; j >= 0; j--) { if (tokens[j].type === "softbreak" || tokens[j].type === "hardbreak") break; if (!tokens[j].content) continue; lastChar = tokens[j].content.charCodeAt(tokens[j].content.length - 1); break; } let nextChar = 32; if (pos < max) nextChar = text.charCodeAt(pos); else for (j = i + 1; j < tokens.length; j++) { if (tokens[j].type === "softbreak" || tokens[j].type === "hardbreak") break; if (!tokens[j].content) continue; nextChar = tokens[j].content.charCodeAt(0); break; } const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctCharCode(lastChar); const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctCharCode(nextChar); const isLastWhiteSpace = isWhiteSpace(lastChar); const isNextWhiteSpace = isWhiteSpace(nextChar); if (isNextWhiteSpace) canOpen = false; else if (isNextPunctChar) { if (!(isLastWhiteSpace || isLastPunctChar)) canOpen = false; } if (isLastWhiteSpace) canClose = false; else if (isLastPunctChar) { if (!(isNextWhiteSpace || isNextPunctChar)) canClose = false; } if (nextChar === 34 && t[0] === "\"") { if (lastChar >= 48 && lastChar <= 57) canClose = canOpen = false; } if (canOpen && canClose) { canOpen = isLastPunctChar; canClose = isNextPunctChar; } if (!canOpen && !canClose) { if (isSingle) addReplacement(replacements, i, t.index, APOSTROPHE); continue; } if (canClose) for (j = stack.length - 1; j >= 0; j--) { let item = stack[j]; if (stack[j].level < thisLevel) break; if (item.single === isSingle && stack[j].level === thisLevel) { item = stack[j]; let openQuote; let closeQuote; if (isSingle) { openQuote = state.md.options.quotes[2]; closeQuote = state.md.options.quotes[3]; } else { openQuote = state.md.options.quotes[0]; closeQuote = state.md.options.quotes[1]; } addReplacement(replacements, i, t.index, closeQuote); addReplacement(replacements, item.token, item.pos, openQuote); stack.length = j; continue OUTER; } } if (canOpen) stack.push({ token: i, pos: t.index, single: isSingle, level: thisLevel }); else if (canClose && isSingle) addReplacement(replacements, i, t.index, APOSTROPHE); } } Object.keys(replacements).forEach(function(tokenIdx) { const idx = Number(tokenIdx); tokens[idx].content = applyReplacements(tokens[idx].content, replacements[tokenIdx]); }); } function smartquotes(state) { if (!state.md.options.typographer) return; for (let blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) { if (state.tokens[blkIdx].type !== "inline" || !QUOTE_TEST_RE.test(state.tokens[blkIdx].content)) continue; process_inlines(state.tokens[blkIdx].children, state); } } //#endregion //#region src/rules_core/text_join.ts function join_alt(tokens) { let curr, last; const max = tokens.length; for (curr = 0; curr < max; curr++) if (tokens[curr].type === "text_special") tokens[curr].type = "text"; for (curr = last = 0; curr < max; curr++) if (tokens[curr].type === "text" && curr + 1 < max && tokens[curr + 1].type === "text") tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; else { if (curr !== last) tokens[last] = tokens[curr]; last++; } if (curr !== last) tokens.length = last; } function text_join(state) { let curr, last; const blockTokens = state.tokens; const l = blockTokens.length; for (let j = 0; j < l; j++) { if (blockTokens[j].type !== "inline") continue; const tokens = blockTokens[j].children; const max = tokens.length; for (curr = 0; curr < max; curr++) { if (tokens[curr].type === "text_special") tokens[curr].type = "text"; if (tokens[curr].children) join_alt(tokens[curr].children); } for (curr = last = 0; curr < max; curr++) if (tokens[curr].type === "text" && curr + 1 < max && tokens[curr + 1].type === "text") tokens[curr + 1].content = tokens[curr].content + tokens[curr + 1].content; else { if (curr !== last) tokens[last] = tokens[curr]; last++; } if (curr !== last) tokens.length = last; } } //#endregion //#region src/parser_core.ts var _rules$2 = [ ["normalize", normalize], ["block", block], ["strip_references", strip_references], ["inline", inline], ["linkify", linkify$1], ["replacements", replace], ["smartquotes", smartquotes], ["text_join", text_join] ]; /** * Top-level rules executor. Glues block/inline parsers and does intermediate * transformations. */ var ParserCore = class { constructor() { _defineProperty( this, /** * {@link Ruler} instance. Keep configuration of core rules. */ "ruler", new Ruler() ); _defineProperty(this, "State", StateCore); for (let i = 0; i < _rules$2.length; i++) this.ruler.push(_rules$2[i][0], _rules$2[i][1]); } /** * Executes core chain rules. */ process(state) { const rules = this.ruler.getRules(""); for (let i = 0, l = rules.length; i < l; i++) rules[i](state); } }; //#endregion //#region src/rules_block/state_block.ts /** Mutable state passed to block rules while tokenizing a source document. */ var StateBlock = class { constructor(src, md, env, tokens) { _defineProperty(this, "bMarks", []); _defineProperty(this, "eMarks", []); _defineProperty(this, "tShift", []); _defineProperty(this, "sCount", []); _defineProperty(this, "bsCount", []); _defineProperty(this, "blkIndent", 0); _defineProperty(this, "line", 0); _defineProperty(this, "lineMax", 0); _defineProperty(this, "tight", false); _defineProperty(this, "listIndent", -1); _defineProperty(this, "parentType", "root"); _defineProperty(this, "level", 0); _defineProperty(this, "Token", Token); this.src = src; this.md = md; this.env = env; this.tokens = tokens; const s = this.src; for (let start = 0, pos = 0, indent = 0, offset = 0, len = s.length, indent_found = false; pos < len; pos++) { const ch = s.charCodeAt(pos); if (!indent_found) if (isSpace(ch)) { indent++; if (ch === 9) offset += 4 - offset % 4; else offset++; continue; } else indent_found = true; if (ch === 10 || pos === len - 1) { if (ch !== 10) pos++; this.bMarks.push(start); this.eMarks.push(pos); this.tShift.push(indent); this.sCount.push(offset); this.bsCount.push(0); indent_found = false; indent = 0; offset = 0; start = pos + 1; } } this.bMarks.push(s.length); this.eMarks.push(s.length); this.tShift.push(0); this.sCount.push(0); this.bsCount.push(0); this.lineMax = this.bMarks.length - 1; } push(type, tag, nesting) { const token = new Token(type, tag, nesting); token.block = true; if (nesting < 0) this.level--; token.level = this.level; if (nesting > 0) this.level++; this.tokens.push(token); return token; } isEmpty(line) { return this.bMarks[line] + this.tShift[line] >= this.eMarks[line]; } skipEmptyLines(from) { for (let max = this.lineMax; from < max; from++) if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) break; return from; } skipSpaces(pos) { for (let max = this.src.length; pos < max; pos++) if (!isSpace(this.src.charCodeAt(pos))) break; return pos; } skipSpacesBack(pos, min) { if (pos <= min) return pos; while (pos > min) if (!isSpace(this.src.charCodeAt(--pos))) return pos + 1; return pos; } skipChars(pos, code) { for (let max = this.src.length; pos < max; pos++) if (this.src.charCodeAt(pos) !== code) break; return pos; } skipCharsBack(pos, code, min) { if (pos <= min) return pos; while (pos > min) if (code !== this.src.charCodeAt(--pos)) return pos + 1; return pos; } getLines(begin, end, indent, keepLastLF) { if (begin >= end) return ""; const queue = new Array(end - begin); for (let i = 0, line = begin; line < end; line++, i++) { let lineIndent = 0; const lineStart = this.bMarks[line]; let first = lineStart; let last; if (line + 1 < end || keepLastLF) last = this.eMarks[line] + 1; else last = this.eMarks[line]; while (first < last && lineIndent < indent) { const ch = this.src.charCodeAt(first); if (isSpace(ch)) if (ch === 9) lineIndent += 4 - (lineIndent + this.bsCount[line]) % 4; else lineIndent++; else if (first - lineStart < this.tShift[line]) lineIndent++; else break; first++; } if (lineIndent > indent) queue[i] = new Array(lineIndent - indent + 1).join(" ") + this.src.slice(first, last); else queue[i] = this.src.slice(first, last); } return queue.join(""); } }; //#endregion //#region src/rules_block/table.ts var MAX_AUTOCOMPLETED_CELLS = 65536; function getLine(state, line) { const pos = state.bMarks[line] + state.tShift[line]; const max = state.eMarks[line]; return state.src.slice(pos, max); } function escapedSplit(str) { const result = []; const max = str.length; let pos = 0; let ch = str.charCodeAt(pos); let isEscaped = false; let lastPos = 0; let current = ""; while (pos < max) { if (ch === 124) if (!isEscaped) { result.push(current + str.substring(lastPos, pos)); current = ""; lastPos = pos + 1; } else { current += str.substring(lastPos, pos - 1); lastPos = pos; } isEscaped = ch === 92; pos++; ch = str.charCodeAt(pos); } result.push(current + str.substring(lastPos)); return result; } function table(state, startLine, endLine, silent) { if (startLine + 2 > endLine) return false; let nextLine = startLine + 1; if (state.sCount[nextLine] < state.blkIndent) return false; if (state.sCount[nextLine] - state.blkIndent >= 4) return false; let pos = state.bMarks[nextLine] + state.tShift[nextLine]; if (pos >= state.eMarks[nextLine]) return false; const firstCh = state.src.charCodeAt(pos++); if (firstCh !== 124 && firstCh !== 45 && firstCh !== 58) return false; if (pos >= state.eMarks[nextLine]) return false; const secondCh = state.src.charCodeAt(pos++); if (secondCh !== 124 && secondCh !== 45 && secondCh !== 58 && !isSpace(secondCh)) return false; if (firstCh === 45 && isSpace(secondCh)) return false; while (pos < state.eMarks[nextLine]) { const ch = state.src.charCodeAt(pos); if (ch !== 124 && ch !== 45 && ch !== 58 && !isSpace(ch)) return false; pos++; } let lineText = getLine(state, startLine + 1); let columns = lineText.split("|"); const aligns = []; for (let i = 0; i < columns.length; i++) { const t = columns[i].trim(); if (!t) if (i === 0 || i === columns.length - 1) continue; else return false; if (!/^:?-+:?$/.test(t)) return false; if (t.charCodeAt(t.length - 1) === 58) aligns.push(t.charCodeAt(0) === 58 ? "center" : "right"); else if (t.charCodeAt(0) === 58) aligns.push("left"); else aligns.push(""); } lineText = getLine(state, startLine).trim(); if (lineText.indexOf("|") === -1) return false; if (state.sCount[startLine] - state.blkIndent >= 4) return false; columns = escapedSplit(lineText); if (columns.length && columns[0] === "") columns.shift(); if (columns.length && columns[columns.length - 1] === "") columns.pop(); const columnCount = columns.length; if (columnCount === 0 || columnCount !== aligns.length) return false; if (silent) return true; const oldParentType = state.parentType; state.parentType = "table"; const terminatorRules = state.md.block.ruler.getRules("blockquote"); const token_to = state.push("table_open", "table", 1); const tableLines = [startLine, 0]; token_to.map = tableLines; const token_tho = state.push("thead_open", "thead", 1); token_tho.map = [startLine, startLine + 1]; const token_htro = state.push("tr_open", "tr", 1); token_htro.map = [startLine, startLine + 1]; for (let i = 0; i < columns.length; i++) { const token_ho = state.push("th_open", "th", 1); if (aligns[i]) token_ho.attrs = [["style", `text-align:${aligns[i]}`]]; const token_il = state.push("inline", "", 0); token_il.content = columns[i].trim(); token_il.children = []; state.push("th_close", "th", -1); } state.push("tr_close", "tr", -1); state.push("thead_close", "thead", -1); let tbodyLines; let autocompletedCells = 0; for (nextLine = startLine + 2; nextLine < endLine; nextLine++) { if (state.sCount[nextLine] < state.blkIndent) break; let terminate = false; for (let i = 0, l = terminatorRules.length; i < l; i++) if (terminatorRules[i](state, nextLine, endLine, true)) { terminate = true; break; } if (terminate) break; lineText = getLine(state, nextLine).trim(); if (!lineText) break; if (state.sCount[nextLine] - state.blkIndent >= 4) break; columns = escapedSplit(lineText); if (columns.length && columns[0] === "") columns.shift(); if (columns.length && columns[columns.length - 1] === "") columns.pop(); autocompletedCells += columnCount - columns.length; if (autocompletedCells > MAX_AUTOCOMPLETED_CELLS) break; if (nextLine === startLine + 2) { const token_tbo = state.push("tbody_open", "tbody", 1); token_tbo.map = tbodyLines = [startLine + 2, 0]; } const token_tro = state.push("tr_open", "tr", 1); token_tro.map = [nextLine, nextLine + 1]; for (let i = 0; i < columnCount; i++) { const token_tdo = state.push("td_open", "td", 1); if (aligns[i]) token_tdo.attrs = [["style", `text-align:${aligns[i]}`]]; const token_il = state.push("inline", "", 0); token_il.content = columns[i] ? columns[i].trim() : ""; token_il.children = []; state.push("td_close", "td", -1); } state.push("tr_close", "tr", -1); } if (