Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed
https://markdown-it.github.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
234 lines
6.4 KiB
234 lines
6.4 KiB
// Process *this* and _that_
|
|
|
|
'use strict';
|
|
|
|
|
|
function isAlphaNum(code) {
|
|
return (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) ||
|
|
(code >= 0x41 /* A */ && code <= 0x5A /* Z */) ||
|
|
(code >= 0x61 /* a */ && code <= 0x7A /* z */);
|
|
}
|
|
|
|
// returns the amount of markers (1, 2, 3, 4+), or -1 on failure;
|
|
// "start" should point at a valid marker
|
|
//
|
|
// note: in case if 4+ markers it is still not a valid emphasis,
|
|
// should be treated as a special case
|
|
function parseStart(state, start) {
|
|
var pos = start, lastChar, count,
|
|
max = state.posMax,
|
|
marker = state.src.charCodeAt(start);
|
|
|
|
lastChar = state.pending.length !== 0 ? state.pending.charCodeAt(state.pending.length - 1) : -1;
|
|
|
|
if (lastChar === marker) { return -1; }
|
|
|
|
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }
|
|
if (pos >= max) { return -1; }
|
|
count = pos - start;
|
|
|
|
// Quoting spec:
|
|
//
|
|
// Character can open emphasis iff
|
|
// 1. it is not part of a sequence of four or more unescaped markers,
|
|
// 2. it is not followed by whitespace,
|
|
// 3. it is "_" and it is not preceded by an ASCII alphanumeric character, and
|
|
// 4. either it is not followed by a marker or it is followed immediately by strong emphasis.
|
|
|
|
if (count >= 4) {
|
|
// check condition 1
|
|
// sequence of four or more unescaped markers can't start an emphasis
|
|
return count;
|
|
}
|
|
|
|
// check condition 2, marker followed by whitespace
|
|
if (state.src.charCodeAt(pos) === 0x20) { return -1; }
|
|
|
|
if (marker === 0x5F /* _ */) {
|
|
// check condition 3, if it's the beginning of the word
|
|
// we need to look back for this
|
|
if (isAlphaNum(lastChar)) { return -1; }
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
// returns the amount of markers (1, 2, 3, 4+), or -1 on failure;
|
|
// "start" should point at a valid marker
|
|
//
|
|
// note: in case if 4+ markers it is still not a valid emphasis,
|
|
// should be treated as a special case
|
|
function parseEnd(state, start) {
|
|
var pos = start, lastChar, count,
|
|
max = state.posMax,
|
|
marker = state.src.charCodeAt(start);
|
|
|
|
lastChar = state.pending.length !== 0 ? state.pending.charCodeAt(state.pending.length - 1) : -1;
|
|
|
|
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }
|
|
count = pos - start;
|
|
|
|
// Quoting spec:
|
|
//
|
|
// Character can close emphasis iff
|
|
// 1. it is not part of a sequence of four or more unescaped markers,
|
|
// 2. it is not preceded by whitespace,
|
|
// 3. it is not "_" or it is not followed by an ASCII alphanumeric character
|
|
|
|
if (count >= 4) {
|
|
// check condition 1
|
|
// sequence of four or more unescaped markers can't start an emphasis
|
|
return count;
|
|
}
|
|
|
|
// check condition 2, marker preceded by whitespace
|
|
if (lastChar === 0x20) { return -1; }
|
|
|
|
if (marker === 0x5F) {
|
|
// check condition 3, if it's the end of the word
|
|
if (pos < max && isAlphaNum(state.src.charCodeAt(pos))) { return -1; }
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
module.exports = function emphasis(state/*, silent*/) {
|
|
var startCount,
|
|
count,
|
|
oldLength,
|
|
oldPending,
|
|
oldFlag,
|
|
found,
|
|
ok,
|
|
oldCount,
|
|
newCount,
|
|
stack,
|
|
breakOutOfOuterLoop,
|
|
max = state.posMax,
|
|
start = state.pos,
|
|
haveLiteralAsterisk,
|
|
marker = state.src.charCodeAt(start);
|
|
|
|
if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; }
|
|
|
|
// skip emphasis in links because it has lower priority, compare:
|
|
// [foo *bar]()*
|
|
// [foo `bar]()`
|
|
if (state.validateInsideEm || state.validateInsideLink) { return false; }
|
|
|
|
startCount = parseStart(state, start);
|
|
if (startCount < 0) { return false; }
|
|
if (startCount >= 4) {
|
|
state.pos += startCount;
|
|
state.pending += state.src.slice(start, startCount);
|
|
return true;
|
|
}
|
|
|
|
if (state.level >= state.options.maxNesting) { return false; }
|
|
|
|
oldLength = state.tokens.length;
|
|
oldPending = state.pending;
|
|
oldFlag = state.validateInsideEm;
|
|
|
|
state.pos = start + startCount;
|
|
stack = [ startCount ];
|
|
state.validateInsideEm = true;
|
|
|
|
while (state.pos < max) {
|
|
if (state.src.charCodeAt(state.pos) === marker && !haveLiteralAsterisk) {
|
|
count = parseEnd(state, state.pos);
|
|
if (count >= 1 && count < 4) {
|
|
oldCount = stack.pop();
|
|
newCount = count;
|
|
|
|
while (oldCount !== newCount) {
|
|
if (oldCount === 3) {
|
|
// e.g. `***foo*`
|
|
stack.push(3 - newCount);
|
|
break;
|
|
}
|
|
|
|
if (newCount < oldCount) {
|
|
// assert(oldCount == 2 && newCount == 1)
|
|
// i.e. `**foo* bar*`
|
|
// not valid for now, but might be in the future
|
|
|
|
// eslint is misconfigured, so it doesn't accept "break MAIN;"
|
|
// here is a crappy workaround
|
|
breakOutOfOuterLoop = true;
|
|
break;
|
|
}
|
|
|
|
// assert(newCount > oldCount)
|
|
newCount -= oldCount;
|
|
|
|
if (stack.length === 0) { break; }
|
|
state.pos += oldCount;
|
|
oldCount = stack.pop();
|
|
}
|
|
|
|
if (breakOutOfOuterLoop) { break; }
|
|
|
|
if (stack.length === 0) {
|
|
startCount = oldCount;
|
|
found = true;
|
|
break;
|
|
}
|
|
state.pos += count;
|
|
continue;
|
|
}
|
|
|
|
count = parseStart(state, state.pos);
|
|
if (count >= 1 && count < 4) {
|
|
stack.push(count);
|
|
state.pos += count;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
ok = state.parser.tokenizeSingle(state);
|
|
|
|
if (ok) {
|
|
haveLiteralAsterisk = false;
|
|
} else {
|
|
haveLiteralAsterisk = state.src.charCodeAt(state.pos) === marker;
|
|
state.pending += state.src[state.pos];
|
|
state.pos++;
|
|
}
|
|
}
|
|
|
|
// restore old state
|
|
state.tokens.length = oldLength;
|
|
state.pending = oldPending;
|
|
state.validateInsideEm = oldFlag;
|
|
|
|
if (!found) {
|
|
// parser failed to find ending tag, so it's not valid emphasis
|
|
state.pos = start;
|
|
return false;
|
|
}
|
|
|
|
// found!
|
|
state.posMax = state.pos;
|
|
state.pos = start + startCount;
|
|
|
|
if (startCount === 2 || startCount === 3) {
|
|
state.push({ type: 'strong_open', level: state.level++ });
|
|
}
|
|
if (startCount === 1 || startCount === 3) {
|
|
state.push({ type: 'em_open', level: state.level++ });
|
|
}
|
|
|
|
state.parser.tokenize(state);
|
|
|
|
if (startCount === 1 || startCount === 3) {
|
|
state.push({ type: 'em_close', level: --state.level });
|
|
}
|
|
if (startCount === 2 || startCount === 3) {
|
|
state.push({ type: 'strong_close', level: --state.level });
|
|
}
|
|
|
|
state.pos = state.posMax + startCount;
|
|
state.posMax = max;
|
|
return true;
|
|
};
|
|
|