1
0
Fork 0

Fixed index.js delimeter logic

* Since the code is suggesting using only $ - $ for inline, $$ - $$ for block math syntax,
 specific delimiter for $ - $ and $$ - $$ is required, not general escape logic.
 also it will fix the issue #2.

* Some code optimization and variable name change was done.

* I found that 'throwOnError:false' option doesn't actually work on katex. They will still throw an error if the syntax is invalid.
 So instead of sending this directly, I fixed this code to catch the error internally, return the original latex token, and log it on the console when throwOnError option is explicitly set to true.
This commit is contained in:
PaulSohn 2016-04-21 20:56:33 +09:00
parent 1922f1d732
commit c58b297f43

319
index.js
View file

@ -11,229 +11,129 @@ for rendering output.
var katex = require('katex'); var katex = require('katex');
//return if we have valid delimiter, '$', is in the position.
function scanDelims(state, start, delimLength) { function isValidDelim(state) {
var pos = start, lastChar, nextChar, count, can_open, can_close, var lastChar, secondLastChar, nextChar, pos = state.pos;
isLastWhiteSpace, isLastPunctChar,
isNextWhiteSpace, isNextPunctChar, if(state.src[pos]!=='$'){
left_flanking = true, return false; //the character $ must be in its position.
right_flanking = true,
max = state.posMax,
isWhiteSpace = state.md.utils.isWhiteSpace,
isPunctChar = state.md.utils.isPunctChar,
isMdAsciiPunct = state.md.utils.isMdAsciiPunct;
// treat beginning of the line as a whitespace
lastChar = start > 0 ? state.src.charCodeAt(start - 1) : 0x20;
if (pos >= max) {
can_open = false;
} }
pos += delimLength; secondLastChar= pos > 1 ? state.src[pos-2] : ' '
lastChar = pos > 0 ? state.src[pos-1] : ' '
count = pos - start; nextChar = pos + 1 < state.src.length ? state.src[pos+1] : ' '
// treat end of the line as a whitespace if(
nextChar = pos < max ? state.src.charCodeAt(pos) : 0x20; lastChar === '\\' //$ we found was escaped.
|| (lastChar === '$' && secondLastChar !== '\\') // $ we found was after $ but not escaped.
isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctChar(String.fromCharCode(lastChar)); || nextChar === '$' //$ we found was actually block delimiter $$.
isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctChar(String.fromCharCode(nextChar)); )
{
isLastWhiteSpace = isWhiteSpace(lastChar); return false;
isNextWhiteSpace = isWhiteSpace(nextChar);
if (isNextWhiteSpace) {
left_flanking = false;
} else if (isNextPunctChar) {
if (!(isLastWhiteSpace || isLastPunctChar)) {
left_flanking = false;
}
} }
if (isLastWhiteSpace) { return true;
right_flanking = false;
} else if (isLastPunctChar) {
if (!(isNextWhiteSpace || isNextPunctChar)) {
right_flanking = false;
}
}
can_open = left_flanking;
can_close = right_flanking;
return {
can_open: can_open,
can_close: can_close,
delims: count
};
} }
function math_inline(state, silent){
function makeMath_inline(open, close) { var start, found = false, token;
return function math_inline(state, silent) { if(silent) { return false; }
var startCount,
found, start = state.pos;
res, if(state.src[start] !== '$'){ return false; }
token, if(!isValidDelim(state)){
closeDelim, state.pos += 1;
max = state.posMax, state.pending += '$';
start = state.pos,
openDelim = state.src.slice(start, start + open.length);
if (openDelim !== open) { return false; }
if (silent) { return false; } // Dont run any pairs in validation mode
res = scanDelims(state, start, openDelim.length);
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 + open.length;
while (state.pos < max) {
closeDelim = state.src.slice(state.pos, state.pos + close.length);
if (closeDelim === close) {
res = scanDelims(state, state.pos, close.length);
if (res.can_close) {
found = true;
break;
}
}
state.md.inline.skipToken(state);
}
if (!found) {
// Parser failed to find ending tag, so it is not a valid math
state.pos = start;
return false;
}
// Found!
state.posMax = state.pos;
state.pos = start + close.length;
// Earlier we checked !silent, but this implementation does not need it
token = state.push('math_inline', 'math', 0);
token.content = state.src.slice(state.pos, state.posMax);
token.markup = open;
state.pos = state.posMax + close.length;
state.posMax = max;
return true; return true;
}; }
state.pos+=1;
while(state.pos < state.posMax){
if(isValidDelim(state)){
found = true;
break;
}
state.md.inline.skipToken(state);
}
if(!found){
// Parser failed to find closing delimiter, so it is not a valid math
state.pos = start;
return false;
}
//found the closing delimiter and state.pos is pointing it
token = state.push('math_inline','math',0);
token.content = state.src.slice(start+1,state.pos).trim();
token.markup = '$';
state.pos += 1;
return true;
} }
function makeMath_block(open, close) { function math_block(state, start, end, silent){
return function math_block(state, startLine, endLine, silent) { var firstLine, lastLine, next, lastPos, found = false, token,
var openDelim, len, params, nextLine, token, firstLine, lastLine, lastLinePos, pos = state.bMarks[start] + state.tShift[start],
haveEndMarker = false, max = state.eMarks[start]
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine]; if(pos + 2 > max){ return false; }
if(state.src.slice(pos,pos+2)!=='$$'){ return false; }
if (pos + open.length > max) { return false; }
pos += 2;
openDelim = state.src.slice(pos, pos + open.length); firstLine = state.src.slice(pos,max);
if (openDelim !== open) { return false; } if(silent){ return true; }
if(firstLine.trim().slice(-2)==='$$'){
pos += open.length; // Single line expression
firstLine = state.src.slice(pos, max); firstLine = firstLine.trim().slice(0, -2);
found = true;
// Since start is found, we can report success here in validation mode }
if (silent) { return true; }
for(next = start; next < end; ){
if (firstLine.trim().slice(-close.length) === close) { if(found){ break; }
// Single line expression next++;
firstLine = firstLine.trim().slice(0, -close.length); pos = state.bMarks[next]+state.tShift[next];
haveEndMarker = true; max = state.eMarks[next];
if(pos < max && state.tShift[next] < state.blkIndent){
break;
} }
if(state.src.slice(pos,max).trim().slice(-2)==='$$'){
// search end of block lastPos = state.src.slice(0,max).lastIndexOf('$$');
nextLine = startLine; lastLine = state.src.slice(pos,lastPos);
found = true;
for (;;) {
if (haveEndMarker) { break; }
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 = 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:
break;
}
if (state.src.slice(pos, max).trim().slice(-close.length) !== close) {
continue;
}
if (state.tShift[nextLine] - state.blkIndent >= 4) {
// closing block math should be indented less then 4 spaces
continue;
}
lastLinePos = state.src.slice(0, max).lastIndexOf(close);
lastLine = state.src.slice(pos, lastLinePos);
pos += lastLine.length + close.length;
// make sure tail has spaces only
pos = state.skipSpaces(pos);
if (pos < max) { continue; }
// found!
haveEndMarker = true;
} }
}
// If math block has heading spaces, they should be removed from its inner block
len = state.tShift[startLine]; if(!found){
return false;
state.line = nextLine + (haveEndMarker ? 1 : 0); }
token = state.push('math_block', 'math', 0); state.line = next + 1;
token.block = true;
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') + token = state.push('math_block', 'math', 0);
state.getLines(startLine + 1, nextLine, len, true) + token.block = true;
(lastLine && lastLine.trim() ? lastLine : ''); token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '')
token.info = params; + state.getLines(start + 1, next, state.tShift[start], true)
token.map = [ startLine, state.line ]; + (lastLine && lastLine.trim() ? lastLine : '');
token.markup = open; token.map = [ start, state.line ];
token.markup = '$$';
return true; return true;
};
} }
module.exports = function math_plugin(md, options) { module.exports = function math_plugin(md, options) {
// Default options // Default options
var inlineOpen = '$',
inlineClose = '$',
blockOpen = '$$',
blockClose = '$$';
options = options || {}; options = options || {};
// set KaTeX as the renderer for markdown-it-simplemath // set KaTeX as the renderer for markdown-it-simplemath
var katexInline = function(latex){ var katexInline = function(latex){
options.displayMode = false; options.displayMode = false;
return katex.renderToString(latex, options); try{
return katex.renderToString(latex, options);
}
catch(error){
if(options.throwOnError){ console.log(error); }
return latex;
}
}; };
var inlineRenderer = function(tokens, idx){ var inlineRenderer = function(tokens, idx){
@ -242,16 +142,19 @@ module.exports = function math_plugin(md, options) {
var katexBlock = function(latex){ var katexBlock = function(latex){
options.displayMode = true; options.displayMode = true;
return katex.renderToString(latex, options); try{
return katex.renderToString(latex, options);
}
catch(error){
if(options.throwOnError){ console.log(error); }
return latex;
}
} }
var blockRenderer = function(tokens, idx){ var blockRenderer = function(tokens, idx){
return katexBlock(tokens[idx].content) + '\n'; return katexBlock(tokens[idx].content) + '\n';
} }
var math_inline = makeMath_inline(inlineOpen, inlineClose);
var math_block = makeMath_block(blockOpen, blockClose);
md.inline.ruler.before('escape', 'math_inline', math_inline); md.inline.ruler.before('escape', 'math_inline', math_inline);
md.block.ruler.after('blockquote', 'math_block', math_block, { md.block.ruler.after('blockquote', 'math_block', math_block, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]