remarkable
Version:
Markdown parser, done right. 100% Commonmark support, extensions, syntax plugins, high speed - all in one.
2,088 lines (1,699 loc) • 125 kB
JavaScript
var textarea;
function decodeEntity(name) {
textarea = textarea || document.createElement('textarea');
textarea.innerHTML = '&' + name + ';';
return textarea.value;
}
/**
* Utility functions
*/
function typeOf(obj) {
return Object.prototype.toString.call(obj);
}
function isString(obj) {
return typeOf(obj) === '[object String]';
}
var hasOwn = Object.prototype.hasOwnProperty;
function has(object, key) {
return object
? hasOwn.call(object, key)
: false;
}
// Extend objects
//
function assign(obj /*from1, from2, from3, ...*/) {
var sources = [].slice.call(arguments, 1);
sources.forEach(function (source) {
if (!source) { return; }
if (typeof source !== 'object') {
throw new TypeError(source + 'must be object');
}
Object.keys(source).forEach(function (key) {
obj[key] = source[key];
});
});
return obj;
}
////////////////////////////////////////////////////////////////////////////////
var UNESCAPE_MD_RE = /\\([\\!"#$%&'()*+,.\/:;<=>?@[\]^_`{|}~-])/g;
function unescapeMd(str) {
if (str.indexOf('\\') < 0) { return str; }
return str.replace(UNESCAPE_MD_RE, '$1');
}
////////////////////////////////////////////////////////////////////////////////
function isValidEntityCode(c) {
/*eslint no-bitwise:0*/
// broken sequence
if (c >= 0xD800 && c <= 0xDFFF) { return false; }
// never used
if (c >= 0xFDD0 && c <= 0xFDEF) { return false; }
if ((c & 0xFFFF) === 0xFFFF || (c & 0xFFFF) === 0xFFFE) { return false; }
// control codes
if (c >= 0x00 && c <= 0x08) { return false; }
if (c === 0x0B) { return false; }
if (c >= 0x0E && c <= 0x1F) { return false; }
if (c >= 0x7F && c <= 0x9F) { return false; }
// out of range
if (c > 0x10FFFF) { return false; }
return true;
}
function fromCodePoint(c) {
/*eslint no-bitwise:0*/
if (c > 0xffff) {
c -= 0x10000;
var surrogate1 = 0xd800 + (c >> 10),
surrogate2 = 0xdc00 + (c & 0x3ff);
return String.fromCharCode(surrogate1, surrogate2);
}
return String.fromCharCode(c);
}
var NAMED_ENTITY_RE = /&([a-z#][a-z0-9]{1,31});/gi;
var DIGITAL_ENTITY_TEST_RE = /^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))/i;
function replaceEntityPattern(match, name) {
var code = 0;
var decoded = decodeEntity(name);
if (name !== decoded) {
return decoded;
} else if (name.charCodeAt(0) === 0x23/* # */ && DIGITAL_ENTITY_TEST_RE.test(name)) {
code = name[1].toLowerCase() === 'x' ?
parseInt(name.slice(2), 16)
:
parseInt(name.slice(1), 10);
if (isValidEntityCode(code)) {
return fromCodePoint(code);
}
}
return match;
}
function replaceEntities(str) {
if (str.indexOf('&') < 0) { return str; }
return str.replace(NAMED_ENTITY_RE, replaceEntityPattern);
}
////////////////////////////////////////////////////////////////////////////////
var HTML_ESCAPE_TEST_RE = /[&<>"]/;
var HTML_ESCAPE_REPLACE_RE = /[&<>"]/g;
var HTML_REPLACEMENTS = {
'&': '&',
'<': '<',
'>': '>',
'"': '"'
};
function replaceUnsafeChar(ch) {
return HTML_REPLACEMENTS[ch];
}
function escapeHtml(str) {
if (HTML_ESCAPE_TEST_RE.test(str)) {
return str.replace(HTML_ESCAPE_REPLACE_RE, replaceUnsafeChar);
}
return str;
}
var utils = /*#__PURE__*/Object.freeze({
isString: isString,
has: has,
assign: assign,
unescapeMd: unescapeMd,
isValidEntityCode: isValidEntityCode,
fromCodePoint: fromCodePoint,
replaceEntities: replaceEntities,
escapeHtml: escapeHtml
});
/**
* Renderer rules cache
*/
var rules = {};
/**
* Blockquotes
*/
rules.blockquote_open = function(/* tokens, idx, options, env */) {
return '<blockquote>\n';
};
rules.blockquote_close = function(tokens, idx /*, options, env */) {
return '</blockquote>' + getBreak(tokens, idx);
};
/**
* Code
*/
rules.code = function(tokens, idx /*, options, env */) {
if (tokens[idx].block) {
return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>' + getBreak(tokens, idx);
}
return '<code>' + escapeHtml(tokens[idx].content) + '</code>';
};
/**
* Fenced code blocks
*/
rules.fence = function(tokens, idx, options, env, instance) {
var token = tokens[idx];
var langClass = '';
var langPrefix = options.langPrefix;
var langName = '', fences, fenceName;
var highlighted;
if (token.params) {
//
// ```foo bar
//
// Try custom renderer "foo" first. That will simplify overwrite
// for diagrams, latex, and any other fenced block with custom look
//
fences = token.params.split(/\s+/g);
fenceName = fences.join(' ');
if (has(instance.rules.fence_custom, fences[0])) {
return instance.rules.fence_custom[fences[0]](tokens, idx, options, env, instance);
}
langName = escapeHtml(replaceEntities(unescapeMd(fenceName)));
langClass = ' class="' + langPrefix + langName + '"';
}
if (options.highlight) {
highlighted = options.highlight.apply(options.highlight, [ token.content ].concat(fences))
|| escapeHtml(token.content);
} else {
highlighted = escapeHtml(token.content);
}
return '<pre><code' + langClass + '>'
+ highlighted
+ '</code></pre>'
+ getBreak(tokens, idx);
};
rules.fence_custom = {};
/**
* Headings
*/
rules.heading_open = function(tokens, idx /*, options, env */) {
return '<h' + tokens[idx].hLevel + '>';
};
rules.heading_close = function(tokens, idx /*, options, env */) {
return '</h' + tokens[idx].hLevel + '>\n';
};
/**
* Horizontal rules
*/
rules.hr = function(tokens, idx, options /*, env */) {
return (options.xhtmlOut ? '<hr />' : '<hr>') + getBreak(tokens, idx);
};
/**
* Bullets
*/
rules.bullet_list_open = function(/* tokens, idx, options, env */) {
return '<ul>\n';
};
rules.bullet_list_close = function(tokens, idx /*, options, env */) {
return '</ul>' + getBreak(tokens, idx);
};
/**
* List items
*/
rules.list_item_open = function(/* tokens, idx, options, env */) {
return '<li>';
};
rules.list_item_close = function(/* tokens, idx, options, env */) {
return '</li>\n';
};
/**
* Ordered list items
*/
rules.ordered_list_open = function(tokens, idx /*, options, env */) {
var token = tokens[idx];
var order = token.order > 1 ? ' start="' + token.order + '"' : '';
return '<ol' + order + '>\n';
};
rules.ordered_list_close = function(tokens, idx /*, options, env */) {
return '</ol>' + getBreak(tokens, idx);
};
/**
* Paragraphs
*/
rules.paragraph_open = function(tokens, idx /*, options, env */) {
return tokens[idx].tight ? '' : '<p>';
};
rules.paragraph_close = function(tokens, idx /*, options, env */) {
var addBreak = !(tokens[idx].tight && idx && tokens[idx - 1].type === 'inline' && !tokens[idx - 1].content);
return (tokens[idx].tight ? '' : '</p>') + (addBreak ? getBreak(tokens, idx) : '');
};
/**
* Links
*/
rules.link_open = function(tokens, idx, options /* env */) {
var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : '';
return '<a href="' + escapeHtml(tokens[idx].href) + '"' + title + target + '>';
};
rules.link_close = function(/* tokens, idx, options, env */) {
return '</a>';
};
/**
* Images
*/
rules.image = function(tokens, idx, options /*, env */) {
var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
var title = tokens[idx].title ? (' title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '"') : '';
var alt = ' alt="' + (tokens[idx].alt ? escapeHtml(replaceEntities(unescapeMd(tokens[idx].alt))) : '') + '"';
var suffix = options.xhtmlOut ? ' /' : '';
return '<img' + src + alt + title + suffix + '>';
};
/**
* Tables
*/
rules.table_open = function(/* tokens, idx, options, env */) {
return '<table>\n';
};
rules.table_close = function(/* tokens, idx, options, env */) {
return '</table>\n';
};
rules.thead_open = function(/* tokens, idx, options, env */) {
return '<thead>\n';
};
rules.thead_close = function(/* tokens, idx, options, env */) {
return '</thead>\n';
};
rules.tbody_open = function(/* tokens, idx, options, env */) {
return '<tbody>\n';
};
rules.tbody_close = function(/* tokens, idx, options, env */) {
return '</tbody>\n';
};
rules.tr_open = function(/* tokens, idx, options, env */) {
return '<tr>';
};
rules.tr_close = function(/* tokens, idx, options, env */) {
return '</tr>\n';
};
rules.th_open = function(tokens, idx /*, options, env */) {
var token = tokens[idx];
return '<th'
+ (token.align ? ' style="text-align:' + token.align + '"' : '')
+ '>';
};
rules.th_close = function(/* tokens, idx, options, env */) {
return '</th>';
};
rules.td_open = function(tokens, idx /*, options, env */) {
var token = tokens[idx];
return '<td'
+ (token.align ? ' style="text-align:' + token.align + '"' : '')
+ '>';
};
rules.td_close = function(/* tokens, idx, options, env */) {
return '</td>';
};
/**
* Bold
*/
rules.strong_open = function(/* tokens, idx, options, env */) {
return '<strong>';
};
rules.strong_close = function(/* tokens, idx, options, env */) {
return '</strong>';
};
/**
* Italicize
*/
rules.em_open = function(/* tokens, idx, options, env */) {
return '<em>';
};
rules.em_close = function(/* tokens, idx, options, env */) {
return '</em>';
};
/**
* Strikethrough
*/
rules.del_open = function(/* tokens, idx, options, env */) {
return '<del>';
};
rules.del_close = function(/* tokens, idx, options, env */) {
return '</del>';
};
/**
* Insert
*/
rules.ins_open = function(/* tokens, idx, options, env */) {
return '<ins>';
};
rules.ins_close = function(/* tokens, idx, options, env */) {
return '</ins>';
};
/**
* Highlight
*/
rules.mark_open = function(/* tokens, idx, options, env */) {
return '<mark>';
};
rules.mark_close = function(/* tokens, idx, options, env */) {
return '</mark>';
};
/**
* Super- and sub-script
*/
rules.sub = function(tokens, idx /*, options, env */) {
return '<sub>' + escapeHtml(tokens[idx].content) + '</sub>';
};
rules.sup = function(tokens, idx /*, options, env */) {
return '<sup>' + escapeHtml(tokens[idx].content) + '</sup>';
};
/**
* Breaks
*/
rules.hardbreak = function(tokens, idx, options /*, env */) {
return options.xhtmlOut ? '<br />\n' : '<br>\n';
};
rules.softbreak = function(tokens, idx, options /*, env */) {
return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n';
};
/**
* Text
*/
rules.text = function(tokens, idx /*, options, env */) {
return escapeHtml(tokens[idx].content);
};
/**
* Content
*/
rules.htmlblock = function(tokens, idx /*, options, env */) {
return tokens[idx].content;
};
rules.htmltag = function(tokens, idx /*, options, env */) {
return tokens[idx].content;
};
/**
* Abbreviations, initialism
*/
rules.abbr_open = function(tokens, idx /*, options, env */) {
return '<abbr title="' + escapeHtml(replaceEntities(tokens[idx].title)) + '">';
};
rules.abbr_close = function(/* tokens, idx, options, env */) {
return '</abbr>';
};
/**
* Footnotes
*/
rules.footnote_ref = function(tokens, idx) {
var n = Number(tokens[idx].id + 1).toString();
var id = 'fnref' + n;
if (tokens[idx].subId > 0) {
id += ':' + tokens[idx].subId;
}
return '<sup class="footnote-ref"><a href="#fn' + n + '" id="' + id + '">[' + n + ']</a></sup>';
};
rules.footnote_block_open = function(tokens, idx, options) {
var hr = options.xhtmlOut
? '<hr class="footnotes-sep" />\n'
: '<hr class="footnotes-sep">\n';
return hr + '<section class="footnotes">\n<ol class="footnotes-list">\n';
};
rules.footnote_block_close = function() {
return '</ol>\n</section>\n';
};
rules.footnote_open = function(tokens, idx) {
var id = Number(tokens[idx].id + 1).toString();
return '<li id="fn' + id + '" class="footnote-item">';
};
rules.footnote_close = function() {
return '</li>\n';
};
rules.footnote_anchor = function(tokens, idx) {
var n = Number(tokens[idx].id + 1).toString();
var id = 'fnref' + n;
if (tokens[idx].subId > 0) {
id += ':' + tokens[idx].subId;
}
return ' <a href="#' + id + '" class="footnote-backref">↩</a>';
};
/**
* Definition lists
*/
rules.dl_open = function() {
return '<dl>\n';
};
rules.dt_open = function() {
return '<dt>';
};
rules.dd_open = function() {
return '<dd>';
};
rules.dl_close = function() {
return '</dl>\n';
};
rules.dt_close = function() {
return '</dt>\n';
};
rules.dd_close = function() {
return '</dd>\n';
};
/**
* Helper functions
*/
function nextToken(tokens, idx) {
if (++idx >= tokens.length - 2) {
return idx;
}
if ((tokens[idx].type === 'paragraph_open' && tokens[idx].tight) &&
(tokens[idx + 1].type === 'inline' && tokens[idx + 1].content.length === 0) &&
(tokens[idx + 2].type === 'paragraph_close' && tokens[idx + 2].tight)) {
return nextToken(tokens, idx + 2);
}
return idx;
}
/**
* Check to see if `\n` is needed before the next token.
*
* @param {Array} `tokens`
* @param {Number} `idx`
* @return {String} Empty string or newline
* @api private
*/
var getBreak = rules.getBreak = function getBreak(tokens, idx) {
idx = nextToken(tokens, idx);
if (idx < tokens.length && tokens[idx].type === 'list_item_close') {
return '';
}
return '\n';
};
/**
* Renderer class. Renders HTML and exposes `rules` to allow
* local modifications.
*/
function Renderer() {
this.rules = assign({}, rules);
// exported helper, for custom rules only
this.getBreak = rules.getBreak;
}
/**
* Render a string of inline HTML with the given `tokens` and
* `options`.
*
* @param {Array} `tokens`
* @param {Object} `options`
* @param {Object} `env`
* @return {String}
* @api public
*/
Renderer.prototype.renderInline = function (tokens, options, env) {
var _rules = this.rules;
var len = tokens.length, i = 0;
var result = '';
while (len--) {
result += _rules[tokens[i].type](tokens, i++, options, env, this);
}
return result;
};
/**
* Render a string of HTML with the given `tokens` and
* `options`.
*
* @param {Array} `tokens`
* @param {Object} `options`
* @param {Object} `env`
* @return {String}
* @api public
*/
Renderer.prototype.render = function (tokens, options, env) {
var _rules = this.rules;
var len = tokens.length, i = -1;
var result = '';
while (++i < len) {
if (tokens[i].type === 'inline') {
result += this.renderInline(tokens[i].children, options, env);
} else {
result += _rules[tokens[i].type](tokens, i, options, env, this);
}
}
return result;
};
/**
* Ruler is a helper class for building responsibility chains from
* parse rules. It allows:
*
* - easy stack rules chains
* - getting main chain and named chains content (as arrays of functions)
*
* Helper methods, should not be used directly.
* @api private
*/
function Ruler() {
// List of added rules. Each element is:
//
// { name: XXX,
// enabled: Boolean,
// fn: Function(),
// alt: [ name2, name3 ] }
//
this.__rules__ = [];
// Cached rule chains.
//
// First level - chain name, '' for default.
// Second level - digital anchor for fast filtering by charcodes.
//
this.__cache__ = null;
}
/**
* Find the index of a rule by `name`.
*
* @param {String} `name`
* @return {Number} Index of the given `name`
* @api private
*/
Ruler.prototype.__find__ = function (name) {
var len = this.__rules__.length;
var i = -1;
while (len--) {
if (this.__rules__[++i].name === name) {
return i;
}
}
return -1;
};
/**
* Build the rules lookup cache
*
* @api private
*/
Ruler.prototype.__compile__ = function () {
var self = this;
var chains = [ '' ];
// collect unique names
self.__rules__.forEach(function (rule) {
if (!rule.enabled) {
return;
}
rule.alt.forEach(function (altName) {
if (chains.indexOf(altName) < 0) {
chains.push(altName);
}
});
});
self.__cache__ = {};
chains.forEach(function (chain) {
self.__cache__[chain] = [];
self.__rules__.forEach(function (rule) {
if (!rule.enabled) {
return;
}
if (chain && rule.alt.indexOf(chain) < 0) {
return;
}
self.__cache__[chain].push(rule.fn);
});
});
};
/**
* Ruler public methods
* ------------------------------------------------
*/
/**
* Replace rule function
*
* @param {String} `name` Rule name
* @param {Function `fn`
* @param {Object} `options`
* @api private
*/
Ruler.prototype.at = function (name, fn, options) {
var idx = this.__find__(name);
var opt = options || {};
if (idx === -1) {
throw new Error('Parser rule not found: ' + name);
}
this.__rules__[idx].fn = fn;
this.__rules__[idx].alt = opt.alt || [];
this.__cache__ = null;
};
/**
* Add a rule to the chain before given the `ruleName`.
*
* @param {String} `beforeName`
* @param {String} `ruleName`
* @param {Function} `fn`
* @param {Object} `options`
* @api private
*/
Ruler.prototype.before = function (beforeName, ruleName, fn, options) {
var idx = this.__find__(beforeName);
var opt = options || {};
if (idx === -1) {
throw new Error('Parser rule not found: ' + beforeName);
}
this.__rules__.splice(idx, 0, {
name: ruleName,
enabled: true,
fn: fn,
alt: opt.alt || []
});
this.__cache__ = null;
};
/**
* Add a rule to the chain after the given `ruleName`.
*
* @param {String} `afterName`
* @param {String} `ruleName`
* @param {Function} `fn`
* @param {Object} `options`
* @api private
*/
Ruler.prototype.after = function (afterName, ruleName, fn, options) {
var idx = this.__find__(afterName);
var opt = options || {};
if (idx === -1) {
throw new Error('Parser rule not found: ' + afterName);
}
this.__rules__.splice(idx + 1, 0, {
name: ruleName,
enabled: true,
fn: fn,
alt: opt.alt || []
});
this.__cache__ = null;
};
/**
* Add a rule to the end of chain.
*
* @param {String} `ruleName`
* @param {Function} `fn`
* @param {Object} `options`
* @return {String}
*/
Ruler.prototype.push = function (ruleName, fn, options) {
var opt = options || {};
this.__rules__.push({
name: ruleName,
enabled: true,
fn: fn,
alt: opt.alt || []
});
this.__cache__ = null;
};
/**
* Enable a rule or list of rules.
*
* @param {String|Array} `list` Name or array of rule names to enable
* @param {Boolean} `strict` If `true`, all non listed rules will be disabled.
* @api private
*/
Ruler.prototype.enable = function (list, strict) {
list = !Array.isArray(list)
? [ list ]
: list;
// In strict mode disable all existing rules first
if (strict) {
this.__rules__.forEach(function (rule) {
rule.enabled = false;
});
}
// Search by name and enable
list.forEach(function (name) {
var idx = this.__find__(name);
if (idx < 0) {
throw new Error('Rules manager: invalid rule name ' + name);
}
this.__rules__[idx].enabled = true;
}, this);
this.__cache__ = null;
};
/**
* Disable a rule or list of rules.
*
* @param {String|Array} `list` Name or array of rule names to disable
* @api private
*/
Ruler.prototype.disable = function (list) {
list = !Array.isArray(list)
? [ list ]
: list;
// Search by name and disable
list.forEach(function (name) {
var idx = this.__find__(name);
if (idx < 0) {
throw new Error('Rules manager: invalid rule name ' + name);
}
this.__rules__[idx].enabled = false;
}, this);
this.__cache__ = null;
};
/**
* Get a rules list as an array of functions.
*
* @param {String} `chainName`
* @return {Object}
* @api private
*/
Ruler.prototype.getRules = function (chainName) {
if (this.__cache__ === null) {
this.__compile__();
}
return this.__cache__[chainName] || [];
};
function block(state) {
if (state.inlineMode) {
state.tokens.push({
type: 'inline',
content: state.src.replace(/\n/g, ' ').trim(),
level: 0,
lines: [ 0, 1 ],
children: []
});
} else {
state.block.parse(state.src, state.options, state.env, state.tokens);
}
}
// Inline parser state
function StateInline(src, parserInline, options, env, outTokens) {
this.src = src;
this.env = env;
this.options = options;
this.parser = parserInline;
this.tokens = outTokens;
this.pos = 0;
this.posMax = this.src.length;
this.level = 0;
this.pending = '';
this.pendingLevel = 0;
this.cache = []; // Stores { start: end } pairs. Useful for backtrack
// optimization of pairs parse (emphasis, strikes).
// Link parser state vars
this.isInLabel = false; // Set true when seek link label - we should disable
// "paired" rules (emphasis, strikes) to not skip
// tailing `]`
this.linkLevel = 0; // Increment for each nesting link. Used to prevent
// nesting in definitions
this.linkContent = ''; // Temporary storage for link url
this.labelUnmatchedScopes = 0; // Track unpaired `[` for link labels
// (backtrack optimization)
}
// Flush pending text
//
StateInline.prototype.pushPending = function () {
this.tokens.push({
type: 'text',
content: this.pending,
level: this.pendingLevel
});
this.pending = '';
};
// Push new token to "stream".
// If pending text exists - flush it as text token
//
StateInline.prototype.push = function (token) {
if (this.pending) {
this.pushPending();
}
this.tokens.push(token);
this.pendingLevel = this.level;
};
// Store value to cache.
// !!! Implementation has parser-specific optimizations
// !!! keys MUST be integer, >= 0; values MUST be integer, > 0
//
StateInline.prototype.cacheSet = function (key, val) {
for (var i = this.cache.length; i <= key; i++) {
this.cache.push(0);
}
this.cache[key] = val;
};
// Get cache value
//
StateInline.prototype.cacheGet = function (key) {
return key < this.cache.length ? this.cache[key] : 0;
};
/**
* Parse link labels
*
* This function assumes that first character (`[`) already matches;
* returns the end of the label.
*
* @param {Object} state
* @param {Number} start
* @api private
*/
function parseLinkLabel(state, start) {
var level, found, marker,
labelEnd = -1,
max = state.posMax,
oldPos = state.pos,
oldFlag = state.isInLabel;
if (state.isInLabel) { return -1; }
if (state.labelUnmatchedScopes) {
state.labelUnmatchedScopes--;
return -1;
}
state.pos = start + 1;
state.isInLabel = true;
level = 1;
while (state.pos < max) {
marker = state.src.charCodeAt(state.pos);
if (marker === 0x5B /* [ */) {
level++;
} else if (marker === 0x5D /* ] */) {
level--;
if (level === 0) {
found = true;
break;
}
}
state.parser.skipToken(state);
}
if (found) {
labelEnd = state.pos;
state.labelUnmatchedScopes = 0;
} else {
state.labelUnmatchedScopes = level - 1;
}
// restore old state
state.pos = oldPos;
state.isInLabel = oldFlag;
return labelEnd;
}
// Parse abbreviation definitions, i.e. `*[abbr]: description`
function parseAbbr(str, parserInline, options, env) {
var state, labelEnd, pos, max, label, title;
if (str.charCodeAt(0) !== 0x2A/* * */) { return -1; }
if (str.charCodeAt(1) !== 0x5B/* [ */) { return -1; }
if (str.indexOf(']:') === -1) { return -1; }
state = new StateInline(str, parserInline, options, env, []);
labelEnd = parseLinkLabel(state, 1);
if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; }
max = state.posMax;
// abbr title is always one line, so looking for ending "\n" here
for (pos = labelEnd + 2; pos < max; pos++) {
if (state.src.charCodeAt(pos) === 0x0A) { break; }
}
label = str.slice(2, labelEnd);
title = str.slice(labelEnd + 2, pos).trim();
if (title.length === 0) { return -1; }
if (!env.abbreviations) { env.abbreviations = {}; }
// prepend ':' to avoid conflict with Object.prototype members
if (typeof env.abbreviations[':' + label] === 'undefined') {
env.abbreviations[':' + label] = title;
}
return pos;
}
function abbr(state) {
var tokens = state.tokens, i, l, content, pos;
if (state.inlineMode) {
return;
}
// Parse inlines
for (i = 1, l = tokens.length - 1; i < l; i++) {
if (tokens[i - 1].type === 'paragraph_open' &&
tokens[i].type === 'inline' &&
tokens[i + 1].type === 'paragraph_close') {
content = tokens[i].content;
while (content.length) {
pos = parseAbbr(content, state.inline, state.options, state.env);
if (pos < 0) { break; }
content = content.slice(pos).trim();
}
tokens[i].content = content;
if (!content.length) {
tokens[i - 1].tight = true;
tokens[i + 1].tight = true;
}
}
}
}
function normalizeLink(url) {
var normalized = replaceEntities(url);
// We shouldn't care about the result of malformed URIs,
// and should not throw an exception.
try {
normalized = decodeURI(normalized);
} catch (err) {}
return encodeURI(normalized);
}
/**
* Parse link destination
*
* - on success it returns a string and updates state.pos;
* - on failure it returns null
*
* @param {Object} state
* @param {Number} pos
* @api private
*/
function parseLinkDestination(state, pos) {
var code, level, link,
start = pos,
max = state.posMax;
if (state.src.charCodeAt(pos) === 0x3C /* < */) {
pos++;
while (pos < max) {
code = state.src.charCodeAt(pos);
if (code === 0x0A /* \n */) { return false; }
if (code === 0x3E /* > */) {
link = normalizeLink(unescapeMd(state.src.slice(start + 1, pos)));
if (!state.parser.validateLink(link)) { return false; }
state.pos = pos + 1;
state.linkContent = link;
return true;
}
if (code === 0x5C /* \ */ && pos + 1 < max) {
pos += 2;
continue;
}
pos++;
}
// no closing '>'
return false;
}
// this should be ... } else { ... branch
level = 0;
while (pos < max) {
code = state.src.charCodeAt(pos);
if (code === 0x20) { break; }
// ascii control chars
if (code < 0x20 || code === 0x7F) { break; }
if (code === 0x5C /* \ */ && pos + 1 < max) {
pos += 2;
continue;
}
if (code === 0x28 /* ( */) {
level++;
if (level > 1) { break; }
}
if (code === 0x29 /* ) */) {
level--;
if (level < 0) { break; }
}
pos++;
}
if (start === pos) { return false; }
link = unescapeMd(state.src.slice(start, pos));
if (!state.parser.validateLink(link)) { return false; }
state.linkContent = link;
state.pos = pos;
return true;
}
/**
* Parse link title
*
* - on success it returns a string and updates state.pos;
* - on failure it returns null
*
* @param {Object} state
* @param {Number} pos
* @api private
*/
function parseLinkTitle(state, pos) {
var code,
start = pos,
max = state.posMax,
marker = state.src.charCodeAt(pos);
if (marker !== 0x22 /* " */ && marker !== 0x27 /* ' */ && marker !== 0x28 /* ( */) { return false; }
pos++;
// if opening marker is "(", switch it to closing marker ")"
if (marker === 0x28) { marker = 0x29; }
while (pos < max) {
code = state.src.charCodeAt(pos);
if (code === marker) {
state.pos = pos + 1;
state.linkContent = unescapeMd(state.src.slice(start + 1, pos));
return true;
}
if (code === 0x5C /* \ */ && pos + 1 < max) {
pos += 2;
continue;
}
pos++;
}
return false;
}
function normalizeReference(str) {
// use .toUpperCase() instead of .toLowerCase()
// here to avoid a conflict with Object.prototype
// members (most notably, `__proto__`)
return str.trim().replace(/\s+/g, ' ').toUpperCase();
}
function parseReference(str, parser, options, env) {
var state, labelEnd, pos, max, code, start, href, title, label;
if (str.charCodeAt(0) !== 0x5B/* [ */) { return -1; }
if (str.indexOf(']:') === -1) { return -1; }
state = new StateInline(str, parser, options, env, []);
labelEnd = parseLinkLabel(state, 0);
if (labelEnd < 0 || str.charCodeAt(labelEnd + 1) !== 0x3A/* : */) { return -1; }
max = state.posMax;
// [label]: destination 'title'
// ^^^ skip optional whitespace here
for (pos = labelEnd + 2; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
// [label]: destination 'title'
// ^^^^^^^^^^^ parse this
if (!parseLinkDestination(state, pos)) { return -1; }
href = state.linkContent;
pos = state.pos;
// [label]: destination 'title'
// ^^^ skipping those spaces
start = pos;
for (pos = pos + 1; pos < max; pos++) {
code = state.src.charCodeAt(pos);
if (code !== 0x20 && code !== 0x0A) { break; }
}
// [label]: destination 'title'
// ^^^^^^^ parse this
if (pos < max && start !== pos && parseLinkTitle(state, pos)) {
title = state.linkContent;
pos = state.pos;
} else {
title = '';
pos = start;
}
// ensure that the end of the line is empty
while (pos < max && state.src.charCodeAt(pos) === 0x20/* space */) { pos++; }
if (pos < max && state.src.charCodeAt(pos) !== 0x0A) { return -1; }
label = normalizeReference(str.slice(1, labelEnd));
if (typeof env.references[label] === 'undefined') {
env.references[label] = { title: title, href: href };
}
return pos;
}
function references(state) {
var tokens = state.tokens, i, l, content, pos;
state.env.references = state.env.references || {};
if (state.inlineMode) {
return;
}
// Scan definitions in paragraph inlines
for (i = 1, l = tokens.length - 1; i < l; i++) {
if (tokens[i].type === 'inline' &&
tokens[i - 1].type === 'paragraph_open' &&
tokens[i + 1].type === 'paragraph_close') {
content = tokens[i].content;
while (content.length) {
pos = parseReference(content, state.inline, state.options, state.env);
if (pos < 0) { break; }
content = content.slice(pos).trim();
}
tokens[i].content = content;
if (!content.length) {
tokens[i - 1].tight = true;
tokens[i + 1].tight = true;
}
}
}
}
function inline(state) {
var tokens = state.tokens, tok, i, l;
// Parse inlines
for (i = 0, l = tokens.length; i < l; i++) {
tok = tokens[i];
if (tok.type === 'inline') {
state.inline.parse(tok.content, state.options, state.env, tok.children);
}
}
}
function footnote_block(state) {
var i, l, j, t, lastParagraph, list, tokens, current, currentLabel,
level = 0,
insideRef = false,
refTokens = {};
if (!state.env.footnotes) { return; }
state.tokens = state.tokens.filter(function(tok) {
if (tok.type === 'footnote_reference_open') {
insideRef = true;
current = [];
currentLabel = tok.label;
return false;
}
if (tok.type === 'footnote_reference_close') {
insideRef = false;
// prepend ':' to avoid conflict with Object.prototype members
refTokens[':' + currentLabel] = current;
return false;
}
if (insideRef) { current.push(tok); }
return !insideRef;
});
if (!state.env.footnotes.list) { return; }
list = state.env.footnotes.list;
state.tokens.push({
type: 'footnote_block_open',
level: level++
});
for (i = 0, l = list.length; i < l; i++) {
state.tokens.push({
type: 'footnote_open',
id: i,
level: level++
});
if (list[i].tokens) {
tokens = [];
tokens.push({
type: 'paragraph_open',
tight: false,
level: level++
});
tokens.push({
type: 'inline',
content: '',
level: level,
children: list[i].tokens
});
tokens.push({
type: 'paragraph_close',
tight: false,
level: --level
});
} else if (list[i].label) {
tokens = refTokens[':' + list[i].label];
}
state.tokens = state.tokens.concat(tokens);
if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') {
lastParagraph = state.tokens.pop();
} else {
lastParagraph = null;
}
t = list[i].count > 0 ? list[i].count : 1;
for (j = 0; j < t; j++) {
state.tokens.push({
type: 'footnote_anchor',
id: i,
subId: j,
level: level
});
}
if (lastParagraph) {
state.tokens.push(lastParagraph);
}
state.tokens.push({
type: 'footnote_close',
level: --level
});
}
state.tokens.push({
type: 'footnote_block_close',
level: --level
});
}
// Enclose abbreviations in <abbr> tags
//
var PUNCT_CHARS = ' \n()[]\'".,!?-';
// from Google closure library
// http://closure-library.googlecode.com/git-history/docs/local_closure_goog_string_string.js.source.html#line1021
function regEscape(s) {
return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1');
}
function abbr2(state) {
var i, j, l, tokens, token, text, nodes, pos, level, reg, m, regText,
blockTokens = state.tokens;
if (!state.env.abbreviations) { return; }
if (!state.env.abbrRegExp) {
regText = '(^|[' + PUNCT_CHARS.split('').map(regEscape).join('') + '])'
+ '(' + Object.keys(state.env.abbreviations).map(function (x) {
return x.substr(1);
}).sort(function (a, b) {
return b.length - a.length;
}).map(regEscape).join('|') + ')'
+ '($|[' + PUNCT_CHARS.split('').map(regEscape).join('') + '])';
state.env.abbrRegExp = new RegExp(regText, 'g');
}
reg = state.env.abbrRegExp;
for (j = 0, l = blockTokens.length; j < l; j++) {
if (blockTokens[j].type !== 'inline') { continue; }
tokens = blockTokens[j].children;
// We scan from the end, to keep position when new tags added.
for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
if (token.type !== 'text') { continue; }
pos = 0;
text = token.content;
reg.lastIndex = 0;
level = token.level;
nodes = [];
while ((m = reg.exec(text))) {
if (reg.lastIndex > pos) {
nodes.push({
type: 'text',
content: text.slice(pos, m.index + m[1].length),
level: level
});
}
nodes.push({
type: 'abbr_open',
title: state.env.abbreviations[':' + m[2]],
level: level++
});
nodes.push({
type: 'text',
content: m[2],
level: level
});
nodes.push({
type: 'abbr_close',
level: --level
});
pos = reg.lastIndex - m[3].length;
}
if (!nodes.length) { continue; }
if (pos < text.length) {
nodes.push({
type: 'text',
content: text.slice(pos),
level: level
});
}
// replace current node
blockTokens[j].children = tokens = [].concat(tokens.slice(0, i), nodes, tokens.slice(i + 1));
}
}
}
// Simple typographical replacements
//
// TODO:
// - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾
// - miltiplication 2 x 4 -> 2 × 4
var RARE_RE = /\+-|\.\.|\?\?\?\?|!!!!|,,|--/;
var SCOPED_ABBR_RE = /\((c|tm|r|p)\)/ig;
var SCOPED_ABBR = {
'c': '©',
'r': '®',
'p': '§',
'tm': '™'
};
function replaceScopedAbbr(str) {
if (str.indexOf('(') < 0) { return str; }
return str.replace(SCOPED_ABBR_RE, function(match, name) {
return SCOPED_ABBR[name.toLowerCase()];
});
}
function replace(state) {
var i, token, text, inlineTokens, blkIdx;
if (!state.options.typographer) { return; }
for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
if (state.tokens[blkIdx].type !== 'inline') { continue; }
inlineTokens = state.tokens[blkIdx].children;
for (i = inlineTokens.length - 1; i >= 0; i--) {
token = inlineTokens[i];
if (token.type === 'text') {
text = token.content;
text = replaceScopedAbbr(text);
if (RARE_RE.test(text)) {
text = text
.replace(/\+-/g, '±')
// .., ..., ....... -> …
// but ?..... & !..... -> ?.. & !..
.replace(/\.{2,}/g, '…').replace(/([?!])…/g, '$1..')
.replace(/([?!]){4,}/g, '$1$1$1').replace(/,{2,}/g, ',')
// em-dash
.replace(/(^|[^-])---([^-]|$)/mg, '$1\u2014$2')
// en-dash
.replace(/(^|\s)--(\s|$)/mg, '$1\u2013$2')
.replace(/(^|[^-\s])--([^-\s]|$)/mg, '$1\u2013$2');
}
token.content = text;
}
}
}
}
// Convert straight quotation marks to typographic ones
//
var QUOTE_TEST_RE = /['"]/;
var QUOTE_RE = /['"]/g;
var PUNCT_RE = /[-\s()\[\]]/;
var APOSTROPHE = '’';
// This function returns true if the character at `pos`
// could be inside a word.
function isLetter(str, pos) {
if (pos < 0 || pos >= str.length) { return false; }
return !PUNCT_RE.test(str[pos]);
}
function replaceAt(str, index, ch) {
return str.substr(0, index) + ch + str.substr(index + 1);
}
function smartquotes(state) {
/*eslint max-depth:0*/
var i, token, text, t, pos, max, thisLevel, lastSpace, nextSpace, item,
canOpen, canClose, j, isSingle, blkIdx, tokens,
stack;
if (!state.options.typographer) { return; }
stack = [];
for (blkIdx = state.tokens.length - 1; blkIdx >= 0; blkIdx--) {
if (state.tokens[blkIdx].type !== 'inline') { continue; }
tokens = state.tokens[blkIdx].children;
stack.length = 0;
for (i = 0; i < tokens.length; i++) {
token = tokens[i];
if (token.type !== 'text' || QUOTE_TEST_RE.test(token.text)) { continue; }
thisLevel = tokens[i].level;
for (j = stack.length - 1; j >= 0; j--) {
if (stack[j].level <= thisLevel) { break; }
}
stack.length = j + 1;
text = token.content;
pos = 0;
max = text.length;
/*eslint no-labels:0,block-scoped-var:0*/
OUTER:
while (pos < max) {
QUOTE_RE.lastIndex = pos;
t = QUOTE_RE.exec(text);
if (!t) { break; }
lastSpace = !isLetter(text, t.index - 1);
pos = t.index + 1;
isSingle = (t[0] === "'");
nextSpace = !isLetter(text, pos);
if (!nextSpace && !lastSpace) {
// middle of word
if (isSingle) {
token.content = replaceAt(token.content, t.index, APOSTROPHE);
}
continue;
}
canOpen = !nextSpace;
canClose = !lastSpace;
if (canClose) {
// this could be a closing quote, rewind the stack to get a match
for (j = stack.length - 1; j >= 0; j--) {
item = stack[j];
if (stack[j].level < thisLevel) { break; }
if (item.single === isSingle && stack[j].level === thisLevel) {
item = stack[j];
if (isSingle) {
tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[2]);
token.content = replaceAt(token.content, t.index, state.options.quotes[3]);
} else {
tokens[item.token].content = replaceAt(tokens[item.token].content, item.pos, state.options.quotes[0]);
token.content = replaceAt(token.content, t.index, state.options.quotes[1]);
}
stack.length = j;
continue OUTER;
}
}
}
if (canOpen) {
stack.push({
token: i,
pos: t.index,
single: isSingle,
level: thisLevel
});
} else if (canClose && isSingle) {
token.content = replaceAt(token.content, t.index, APOSTROPHE);
}
}
}
}
}
/**
* Core parser `rules`
*/
var _rules = [
[ 'block', block ],
[ 'abbr', abbr ],
[ 'references', references ],
[ 'inline', inline ],
[ 'footnote_tail', footnote_block ],
[ 'abbr2', abbr2 ],
[ 'replacements', replace ],
[ 'smartquotes', smartquotes ],
];
/**
* Class for top level (`core`) parser rules
*
* @api private
*/
function Core() {
this.options = {};
this.ruler = new Ruler();
for (var i = 0; i < _rules.length; i++) {
this.ruler.push(_rules[i][0], _rules[i][1]);
}
}
/**
* Process rules with the given `state`
*
* @param {Object} `state`
* @api private
*/
Core.prototype.process = function (state) {
var i, l, rules;
rules = this.ruler.getRules('');
for (i = 0, l = rules.length; i < l; i++) {
rules[i](state);
}
};
// Parser state class
function StateBlock(src, parser, options, env, tokens) {
var ch, s, start, pos, len, indent, indent_found;
this.src = src;
// Shortcuts to simplify nested calls
this.parser = parser;
this.options = options;
this.env = env;
//
// Internal state vartiables
//
this.tokens = tokens;
this.bMarks = []; // line begin offsets for fast jumps
this.eMarks = []; // line end offsets for fast jumps
this.tShift = []; // indent for each line
// block parser variables
this.blkIndent = 0; // required block content indent
// (for example, if we are in list)
this.line = 0; // line index in src
this.lineMax = 0; // lines count
this.tight = false; // loose/tight mode for lists
this.parentType = 'root'; // if `list`, block parser stops on two newlines
this.ddIndent = -1; // indent of the current dd block (-1 if there isn't any)
this.level = 0;
// renderer
this.result = '';
// Create caches
// Generate markers.
s = this.src;
indent = 0;
indent_found = false;
for (start = pos = indent = 0, len = s.length; pos < len; pos++) {
ch = s.charCodeAt(pos);
if (!indent_found) {
if (ch === 0x20/* space */) {
indent++;
continue;
} else {
indent_found = true;
}
}
if (ch === 0x0A || pos === len - 1) {
if (ch !== 0x0A) { pos++; }
this.bMarks.push(start);
this.eMarks.push(pos);
this.tShift.push(indent);
indent_found = false;
indent = 0;
start = pos + 1;
}
}
// Push fake entry to simplify cache bounds checks
this.bMarks.push(s.length);
this.eMarks.push(s.length);
this.tShift.push(0);
this.lineMax = this.bMarks.length - 1; // don't count last fake line
}
StateBlock.prototype.isEmpty = function isEmpty(line) {
return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];
};
StateBlock.prototype.skipEmptyLines = function skipEmptyLines(from) {
for (var max = this.lineMax; from < max; from++) {
if (this.bMarks[from] + this.tShift[from] < this.eMarks[from]) {
break;
}
}
return from;
};
// Skip spaces from given position.
StateBlock.prototype.skipSpaces = function skipSpaces(pos) {
for (var max = this.src.length; pos < max; pos++) {
if (this.src.charCodeAt(pos) !== 0x20/* space */) { break; }
}
return pos;
};
// Skip char codes from given position
StateBlock.prototype.skipChars = function skipChars(pos, code) {
for (var max = this.src.length; pos < max; pos++) {
if (this.src.charCodeAt(pos) !== code) { break; }
}
return pos;
};
// Skip char codes reverse from given position - 1
StateBlock.prototype.skipCharsBack = function skipCharsBack(pos, code, min) {
if (pos <= min) { return pos; }
while (pos > min) {
if (code !== this.src.charCodeAt(--pos)) { return pos + 1; }
}
return pos;
};
// cut lines range from source.
StateBlock.prototype.getLines = function getLines(begin, end, indent, keepLastLF) {
var i, first, last, queue, shift,
line = begin;
if (begin >= end) {
return '';
}
// Opt: don't use push queue for single line;
if (line + 1 === end) {
first = this.bMarks[line] + Math.min(this.tShift[line], indent);
last = keepLastLF ? this.eMarks[line] + 1 : this.eMarks[line];
return this.src.slice(first, last);
}
queue = new Array(end - begin);
for (i = 0; line < end; line++, i++) {
shift = this.tShift[line];
if (shift > indent) { shift = indent; }
if (shift < 0) { shift = 0; }
first = this.bMarks[line] + shift;
if (line + 1 < end || keepLastLF) {
// No need for bounds check because we have fake entry on tail.
last = this.eMarks[line] + 1;
} else {
last = this.eMarks[line];
}
queue[i] = this.src.slice(first, last);
}
return queue.join('');
};
// Code block (4 spaces padded)
function code(state, startLine, endLine/*, silent*/) {
var nextLine, last;
if (state.tShift[startLine] - state.blkIndent < 4) { return false; }
last = nextLine = startLine + 1;
while (nextLine < endLine) {
if (state.isEmpty(nextLine)) {
nextLine++;
continue;
}
if (state.tShift[nextLine] - state.blkIndent >= 4) {
nextLine++;
last = nextLine;
continue;
}
break;
}
state.line = nextLine;
state.tokens.push({
type: 'code',
content: state.getLines(startLine, last, 4 + state.blkIndent, true),
block: true,
lines: [ startLine, state.line ],
level: state.level
});
return true;
}
// fences (``` lang, ~~~ lang)
function fences(state, startLine, endLine, silent) {
var marker, len, params, nextLine, mem,
haveEndMarker = false,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
if (pos + 3 > max) { return false; }
marker = state.src.charCodeAt(pos);
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
return false;
}
// scan marker length
mem = pos;
pos = state.skipChars(pos, marker);
len = pos - mem;
if (len < 3) { return false; }
params = state.src.slice(pos, max).trim();
if (params.indexOf('`') >= 0) { return false; }
// Since start is found, we can report success here in validation mode
if (silent) { return true; }
// search end of block
nextLine = startLine;
for (;;) {
nextLine++;
if (nextLine >= endLine) {
// unclosed block should be autoclosed by end of document.
// also block seems to be autoclosed by end of parent
break;
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (pos < max && state.tShift[nextLine] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
// - ```
// test
break;
}
if (state.src.charCodeAt(pos) !== marker) { continue; }
if (state.tShift[nextLine] - state.blkIndent >= 4) {
// closing fence should be indented less than 4 spaces
continue;
}
pos = state.skipChars(pos, marker);
// closing code fence must be at least as long as the opening one
if (pos - mem < len) { continue; }
// make sure tail has spaces only
pos = state.skipSpaces(pos);
if (pos < max) { continue; }
haveEndMarker = true;
// found!
break;
}
// If a fence has heading spaces, they should be removed from its inner block
len = state.tShift[startLine];
state.line = nextLine + (haveEndMarker ? 1 : 0);
state.tokens.push({
type: 'fence',
params: params,
content: state.getLines(startLine + 1, nextLine, len, true),
lines: [ startLine, state.line ],
level: state.level
});
return true;
}
// Block quotes
function blockquote(state, startLine, endLine, silent) {
var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines,
terminatorRules,
i, l, terminate,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
if (pos > max) { return false; }
// check the block quote marker
if (state.src.charCodeAt(pos++) !== 0x3E/* > */) { return false; }
if (state.level >= state.options.maxNesting) { return false; }
// we know that it's going to be a valid blockquote,
// so no point trying to find the end of it in silent mode
if (silent) { return true; }
// skip one optional space after '>'
if (state.src.charCodeAt(pos) === 0x20) { pos++; }
oldIndent = state.blkIndent;
state.blkIndent = 0;
oldBMarks = [ state.bMarks[startLine] ];
state.bMarks[startLine] = pos;
// check if we have an empty blockquote
pos = pos < max ? state.skipSpaces(pos) : pos;
lastLineEmpty = pos >= max;
oldTShift = [ state.tShift[startLine] ];
state.tShift[startLine] = pos - state.bMarks[startLine];
terminatorRules = state.parser.ruler.getRules('blockquote');
// Search the end of the block
//
// Block ends with either:
// 1. an empty line outside:
// ```
// > test
//
// ```
// 2. an empty line inside:
// ```
// >
// test
// ```
// 3. another tag
// ```
// > test
// - - -
// ```
for (nextLine = startLine + 1; nextLine < endLine; nextLine++) {
pos = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (pos >= max) {
// Case 1: line is not inside the blockquote, and this line is empty.
break;
}
if (state.src.charCodeAt(pos++) === 0x3E/* > */) {
// This line is inside the blockquote.
// skip one optional space after '>'
if (state.src.charCodeAt(pos) === 0x20) { pos++; }
oldBMarks.push(state.bMarks[nextLine]);
state.bMarks[nextLine] = pos;
pos = pos < max ? state.skipSpaces(pos) : pos