|
|
|
// Process *this* and _that_
|
|
|
|
//
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
var isWhiteSpace = require('../common/utils').isWhiteSpace;
|
|
|
|
var isPunctChar = require('../common/utils').isPunctChar;
|
|
|
|
var isMdAsciiPunct = require('../common/utils').isMdAsciiPunct;
|
|
|
|
|
|
|
|
|
|
|
|
// parse sequence of emphasis markers,
|
|
|
|
// "start" should point at a valid marker
|
|
|
|
function scanDelims(state, start) {
|
|
|
|
var pos = start, lastChar, nextChar, count, can_open, can_close,
|
|
|
|
isLastWhiteSpace, isLastPunctChar,
|
|
|
|
isNextWhiteSpace, isNextPunctChar,
|
|
|
|
left_flanking = true,
|
|
|
|
right_flanking = true,
|
|
|
|
max = state.posMax,
|
|
|
|
marker = state.src.charCodeAt(start);
|
|
|
|
|
|
|
|
// treat beginning of the line as a whitespace
|
|
|
|
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20;
|
|
|
|
|
|
|
|
while (pos < max && state.src.charCodeAt(pos) === marker) { pos++; }
|
|
|
|
|
|
|
|
count = pos - start;
|
|
|
|
|
|
|
|
// treat end of the line as a whitespace
|
|
|
|
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20;
|
|
|
|
|
|
|
|
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar));
|
|
|
|
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar));
|
|
|
|
|
|
|
|
isLastWhiteSpace = isWhiteSpace(lastChar);
|
|
|
|
isNextWhiteSpace = isWhiteSpace(nextChar);
|
|
|
|
|
|
|
|
if (isNextWhiteSpace) {
|
|
|
|
left_flanking = false;
|
|
|
|
} else if (isNextPunctChar) {
|
|
|
|
if (!(isLastWhiteSpace || isLastPunctChar)) {
|
|
|
|
left_flanking = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isLastWhiteSpace) {
|
|
|
|
right_flanking = false;
|
|
|
|
} else if (isLastPunctChar) {
|
|
|
|
if (!(isNextWhiteSpace || isNextPunctChar)) {
|
|
|
|
right_flanking = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (marker === 0x5F /* _ */) {
|
|
|
|
// "_" inside a word can neither open nor close an emphasis
|
|
|
|
can_open = left_flanking && (!right_flanking || isLastPunctChar);
|
|
|
|
can_close = right_flanking && (!left_flanking || isNextPunctChar);
|
|
|
|
} else {
|
|
|
|
can_open = left_flanking;
|
|
|
|
can_close = right_flanking;
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
can_open: can_open,
|
|
|
|
can_close: can_close,
|
|
|
|
delims: count
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = function emphasis(state, silent) {
|
|
|
|
var startCount,
|
|
|
|
count,
|
|
|
|
found,
|
|
|
|
oldCount,
|
|
|
|
newCount,
|
|
|
|
stack,
|
|
|
|
res,
|
|
|
|
token,
|
|
|
|
max = state.posMax,
|
|
|
|
start = state.pos,
|
|
|
|
marker = state.src.charCodeAt(start);
|
|
|
|
|
|
|
|
if (marker !== 0x5F/* _ */ && marker !== 0x2A /* * */) { return false; }
|
|
|
|
if (silent) { return false; } // don't run any pairs in validation mode
|
|
|
|
|
|
|
|
res = scanDelims(state, start);
|
|
|
|
startCount = res.delims;
|
|
|
|
if (!res.can_open) {
|
|
|
|
state.pos += startCount;
|
|
|
|
// Earlier we checked !silent, but this implementation does not need it
|
|
|
|
state.pending += state.src.slice(start, state.pos);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.pos = start + startCount;
|
|
|
|
stack = [ startCount ];
|
|
|
|
|
|
|
|
while (state.pos < max) {
|
|
|
|
if (state.src.charCodeAt(state.pos) === marker) {
|
|
|
|
res = scanDelims(state, state.pos);
|
|
|
|
count = res.delims;
|
|
|
|
if (res.can_close) {
|
|
|
|
oldCount = stack.pop();
|
|
|
|
newCount = count;
|
|
|
|
|
|
|
|
while (oldCount !== newCount) {
|
|
|
|
if (newCount < oldCount) {
|
|
|
|
stack.push(oldCount - newCount);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// assert(newCount > oldCount)
|
|
|
|
newCount -= oldCount;
|
|
|
|
|
|
|
|
if (stack.length === 0) { break; }
|
|
|
|
state.pos += oldCount;
|
|
|
|
oldCount = stack.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stack.length === 0) {
|
|
|
|
startCount = oldCount;
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
state.pos += count;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res.can_open) { stack.push(count); }
|
|
|
|
state.pos += count;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
state.md.inline.skipToken(state);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// Earlier we checked !silent, but this implementation does not need it
|
|
|
|
|
|
|
|
// we have `startCount` starting and ending markers,
|
|
|
|
// now trying to serialize them into tokens
|
|
|
|
for (count = startCount; count > 1; count -= 2) {
|
|
|
|
token = state.push('strong_open', 'strong', 1);
|
|
|
|
token.markup = String.fromCharCode(marker) + String.fromCharCode(marker);
|
|
|
|
}
|
|
|
|
if (count % 2) {
|
|
|
|
token = state.push('em_open', 'em', 1);
|
|
|
|
token.markup = String.fromCharCode(marker);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.md.inline.tokenize(state);
|
|
|
|
|
|
|
|
if (count % 2) {
|
|
|
|
token = state.push('em_close', 'em', -1);
|
|
|
|
token.markup = String.fromCharCode(marker);
|
|
|
|
}
|
|
|
|
for (count = startCount; count > 1; count -= 2) {
|
|
|
|
token = state.push('strong_close', 'strong', -1);
|
|
|
|
token.markup = String.fromCharCode(marker) + String.fromCharCode(marker);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.pos = state.posMax + startCount;
|
|
|
|
state.posMax = max;
|
|
|
|
return true;
|
|
|
|
};
|