|
|
|
// Inline parser
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
var Ruler = require('./ruler');
|
|
|
|
var StateInline = require('./rules_inline/state_inline');
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Parser rules
|
|
|
|
|
|
|
|
var rules = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Pure text
|
|
|
|
rules.push(require('./rules_inline/text'));
|
|
|
|
rules.push(require('./rules_inline/newline'));
|
|
|
|
rules.push(require('./rules_inline/escape'));
|
|
|
|
rules.push(require('./rules_inline/backticks'));
|
|
|
|
rules.push(require('./rules_inline/strikethrough'));
|
|
|
|
rules.push(require('./rules_inline/emphasis'));
|
|
|
|
rules.push(require('./rules_inline/links'));
|
|
|
|
rules.push(require('./rules_inline/autolink'));
|
|
|
|
rules.push(require('./rules_inline/htmltag'));
|
|
|
|
rules.push(require('./rules_inline/entity'));
|
|
|
|
rules.push(require('./rules_inline/escape_html_char'));
|
|
|
|
|
|
|
|
var BAD_PROTOCOLS = [ 'vbscript', 'javascript', 'file' ];
|
|
|
|
|
|
|
|
function validateLink(url) {
|
|
|
|
var str = '';
|
|
|
|
|
|
|
|
try {
|
|
|
|
str = decodeURI(url).trim().toLowerCase();
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
if (!str) { return false; }
|
|
|
|
|
|
|
|
if (str.indexOf(':') >= 0 && BAD_PROTOCOLS.indexOf(str.split(':')[0]) >= 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inline Parser class
|
|
|
|
//
|
|
|
|
function ParserInline() {
|
|
|
|
this._rules = [];
|
|
|
|
|
|
|
|
// Rule to skip pure text
|
|
|
|
// - '{$%@}' reserved for extentions
|
|
|
|
// - '<>"' added for internal html escaping
|
|
|
|
this.textMatch = /^[^\n\\`*_\[\]!&{}$%@<>"~]+/;
|
|
|
|
|
|
|
|
// By default CommonMark allows too much in links
|
|
|
|
// If you need to restrict it - override this with your validator.
|
|
|
|
this.validateLink = validateLink;
|
|
|
|
|
|
|
|
this.ruler = new Ruler(this.rulesUpdate.bind(this));
|
|
|
|
|
|
|
|
for (var i = 0; i < rules.length; i++) {
|
|
|
|
this.ruler.after(rules[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ParserInline.prototype.rulesUpdate = function () {
|
|
|
|
this._rules = this.ruler.getRules();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Skip single token by running all rules in validation mode;
|
|
|
|
// returns `true` if any rule reported success
|
|
|
|
//
|
|
|
|
ParserInline.prototype.skipToken = function (state) {
|
|
|
|
var i, cached_pos, pos = state.pos,
|
|
|
|
rules = this._rules,
|
|
|
|
len = this._rules.length;
|
|
|
|
|
|
|
|
if ((cached_pos = state.cacheGet(pos)) > 0) {
|
|
|
|
state.pos = cached_pos;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
if (rules[i](state, true)) {
|
|
|
|
state.cacheSet(pos, state.pos);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state.pos++;
|
|
|
|
state.cacheSet(pos, state.pos);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Generate tokens for input range
|
|
|
|
//
|
|
|
|
ParserInline.prototype.tokenize = function (state) {
|
|
|
|
var ok, i,
|
|
|
|
rules = this._rules,
|
|
|
|
len = this._rules.length,
|
|
|
|
end = state.posMax;
|
|
|
|
|
|
|
|
while (state.pos < end) {
|
|
|
|
|
|
|
|
// Try all possible rules.
|
|
|
|
// On success, rule should:
|
|
|
|
//
|
|
|
|
// - update `state.pos`
|
|
|
|
// - update `state.tokens`
|
|
|
|
// - return true
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
ok = rules[i](state, false);
|
|
|
|
if (ok) { break; }
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
if (state.pos >= end) { break; }
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.pending += state.src[state.pos++];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.pending) {
|
|
|
|
state.pushPending();
|
|
|
|
}
|
|
|
|
|
|
|
|
return state.tokens;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Parse input string.
|
|
|
|
//
|
|
|
|
ParserInline.prototype.parse = function (str, options, env) {
|
|
|
|
var state = new StateInline(str, this, options, env);
|
|
|
|
|
|
|
|
this.tokenize(state);
|
|
|
|
|
|
|
|
if (options.linkify) {
|
|
|
|
this.linkifier.process(state);
|
|
|
|
}
|
|
|
|
if (options.typographer) {
|
|
|
|
this.typographer.process(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
return state.tokens;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = ParserInline;
|