diff --git a/index.js b/index.js index 92c0866..64e6723 100644 --- a/index.js +++ b/index.js @@ -7,167 +7,186 @@ It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX for rendering output. */ +/*jslint node: true */ 'use strict'; var katex = require('katex'); -//return if we have valid delimiter, '$', is in the position. -function isValidDelim(state) { - var lastChar, secondLastChar, nextChar, pos = state.pos; +// Test if potential opening or closing delimieter +// Assumes that there is a "$" at state.src[pos] +function isValidDelim(state, pos) { + var prevChar, nextChar, + max = state.posMax, + can_open = true, + can_close = true; - if(state.src[pos]!=='$'){ - return false; //the character $ must be in its position. - } + prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1; + nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1; - secondLastChar= pos > 1 ? state.src[pos-2] : ' ' - lastChar = pos > 0 ? state.src[pos-1] : ' ' - nextChar = pos + 1 < state.src.length ? state.src[pos+1] : ' ' + // Check non-whitespace conditions for opening and closing, and + // check that closing delimeter isn't followed by a number + if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ || + (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) { + can_close = false; + } + if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) { + can_open = false; + } - if( - lastChar === '\\' //$ we found was escaped. - || (lastChar === '$' && secondLastChar !== '\\') // $ we found was after $ but not escaped. - || nextChar === '$' //$ we found was actually block delimiter $$. - ) - { - return false; - } - - return true; + return { + can_open: can_open, + can_close: can_close + }; } -function math_inline(state, silent){ - var start, found = false, token; - if(silent) { return false; } +function math_inline(state, silent) { + var start, match, token, res, + pos = state.pos; - start = state.pos; - if(state.src[start] !== '$'){ return false; } - if(!isValidDelim(state)){ - state.pos += 1; - state.pending += '$'; - return true; - } - state.pos+=1; + if (state.src[state.pos] !== "$") { return false; } - while(state.pos < state.posMax){ - if(isValidDelim(state)){ - found = true; - break; + res = isValidDelim(state, pos); + if (!res.can_open) { + if (!silent) { state.pending += "$"; } + state.pos += 1; + return true; } - 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; - } - if (start + 1 === state.pos) { - // There is nothing between the delimiters -- don't match. - state.pos = start; - return false; - } + start = state.pos + 1; + match = state.src.indexOf("$", start); - //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 = '$'; + // No closing delimter found. Consume $ and continue. + if (match === -1) { + if (!silent) { state.pending += "$"; } + state.pos = start; + return true; + } - state.pos += 1; - return true; + res = isValidDelim(state, match); + + // We only will look at the very next delimeter while searching + // for closing delimeters. As a consequnce, we will never send + // KaTeX a $ inside of math mode, even if escaped. The other alternative + // would otherwise require escaping commonly used things such as + // \int, \sum, etc... Perhaps there is a way to find a better solution. + // Such as counting the number of \\\\\\\$ and if odd escape $ and remove + // and leading \ or otherwise leave as is. + + if (!res.can_close) { + if (!silent) { state.pending += "$"; } + state.pos = start; + return true; + } + + // Check if we have empty content, ie: $$. Do not parse. + if (match - start === 0) { + if (!silent) { state.pending += "$$"; } + state.pos = start + 1; + return true; + } + + if (!silent) { + token = state.push('math_inline', 'math', 0); + token.markup = "$"; + token.content = state.src.slice(start, match); + } + + state.pos = match + 1; + return true; } function math_block(state, start, end, silent){ - var firstLine, lastLine, next, lastPos, found = false, token, - pos = state.bMarks[start] + state.tShift[start], - max = state.eMarks[start] + var firstLine, lastLine, next, lastPos, found = false, token, + pos = state.bMarks[start] + state.tShift[start], + max = state.eMarks[start] - if(pos + 2 > max){ return false; } - if(state.src.slice(pos,pos+2)!=='$$'){ return false; } + if(pos + 2 > max){ return false; } + if(state.src.slice(pos,pos+2)!=='$$'){ return false; } - pos += 2; - firstLine = state.src.slice(pos,max); + pos += 2; + firstLine = state.src.slice(pos,max); - if(silent){ return true; } - if(firstLine.trim().slice(-2)==='$$'){ - // Single line expression - firstLine = firstLine.trim().slice(0, -2); - found = true; - } - - for(next = start; !found; ){ - - next++; - - if(next >= end){ break; } - - pos = state.bMarks[next]+state.tShift[next]; - max = state.eMarks[next]; - - if(pos < max && state.tShift[next] < state.blkIndent){ - // non-empty line with negative indent should stop the list: - break; + if(silent){ return true; } + if(firstLine.trim().slice(-2)==='$$'){ + // Single line expression + firstLine = firstLine.trim().slice(0, -2); + found = true; } - if(state.src.slice(pos,max).trim().slice(-2)==='$$'){ - lastPos = state.src.slice(0,max).lastIndexOf('$$'); - lastLine = state.src.slice(pos,lastPos); - found = true; + for(next = start; !found; ){ + + next++; + + if(next >= end){ break; } + + pos = state.bMarks[next]+state.tShift[next]; + max = state.eMarks[next]; + + if(pos < max && state.tShift[next] < state.blkIndent){ + // non-empty line with negative indent should stop the list: + break; + } + + if(state.src.slice(pos,max).trim().slice(-2)==='$$'){ + lastPos = state.src.slice(0,max).lastIndexOf('$$'); + lastLine = state.src.slice(pos,lastPos); + found = true; + } + } - } + state.line = next + 1; - state.line = next + 1; - - token = state.push('math_block', 'math', 0); - token.block = true; - token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') + token = state.push('math_block', 'math', 0); + token.block = true; + token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') + state.getLines(start + 1, next, state.tShift[start], true) + (lastLine && lastLine.trim() ? lastLine : ''); - token.map = [ start, state.line ]; - token.markup = '$$'; - return true; + token.map = [ start, state.line ]; + token.markup = '$$'; + return true; } module.exports = function math_plugin(md, options) { - // Default options + // Default options - options = options || {}; + options = options || {}; - // set KaTeX as the renderer for markdown-it-simplemath - var katexInline = function(latex){ - options.displayMode = false; - try{ - return katex.renderToString(latex, options); + // set KaTeX as the renderer for markdown-it-simplemath + var katexInline = function(latex){ + options.displayMode = false; + try{ + return katex.renderToString(latex, options); + } + catch(error){ + if(options.throwOnError){ console.log(error); } + return latex; + } + }; + + var inlineRenderer = function(tokens, idx){ + return katexInline(tokens[idx].content); + }; + + var katexBlock = function(latex){ + options.displayMode = true; + try{ + return "

" + katex.renderToString(latex, options) + "

"; + } + catch(error){ + if(options.throwOnError){ console.log(error); } + return latex; + } } - catch(error){ - if(options.throwOnError){ console.log(error); } - return latex; + + var blockRenderer = function(tokens, idx){ + return katexBlock(tokens[idx].content) + '\n'; } - }; - var inlineRenderer = function(tokens, idx){ - return katexInline(tokens[idx].content); - }; - - var katexBlock = function(latex){ - options.displayMode = true; - try{ - return "

" + katex.renderToString(latex, options) + "

"; - } - catch(error){ - if(options.throwOnError){ console.log(error); } - return latex; - } - } - - var blockRenderer = function(tokens, idx){ - return katexBlock(tokens[idx].content) + '\n'; - } - - md.inline.ruler.before('escape', 'math_inline', math_inline); - md.block.ruler.after('blockquote', 'math_block', math_block, { - alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] - }); - md.renderer.rules.math_inline = inlineRenderer; - md.renderer.rules.math_block = blockRenderer; + md.inline.ruler.after('escape', 'math_inline', math_inline); + md.block.ruler.after('blockquote', 'math_block', math_block, { + alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] + }); + md.renderer.rules.math_inline = inlineRenderer; + md.renderer.rules.math_block = blockRenderer; }; diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..c3cf1a7 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,23 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/bin/nodejs', '/usr/bin/npm', 'run', 'lint' ] +2 info using npm@2.14.20 +3 info using node@v4.4.0 +4 verbose stack Error: missing script: lint +4 verbose stack at run (/usr/lib/node_modules/npm/lib/run-script.js:142:19) +4 verbose stack at /usr/lib/node_modules/npm/lib/run-script.js:58:5 +4 verbose stack at /usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:345:5 +4 verbose stack at checkBinReferences_ (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:309:45) +4 verbose stack at final (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:343:3) +4 verbose stack at then (/usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:113:5) +4 verbose stack at /usr/lib/node_modules/npm/node_modules/read-package-json/read-json.js:300:12 +4 verbose stack at /usr/lib/node_modules/npm/node_modules/graceful-fs/graceful-fs.js:78:16 +4 verbose stack at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3) +5 verbose cwd /home/breeden/github/markdown-it-katex +6 error Linux 3.16.0-4-amd64 +7 error argv "/usr/bin/nodejs" "/usr/bin/npm" "run" "lint" +8 error node v4.4.0 +9 error npm v2.14.20 +10 error missing script: lint +11 error If you need help, you may report this error at: +11 error +12 verbose exit [ 1, true ] diff --git a/test/fixtures/default.txt b/test/fixtures/default.txt index ef24ac3..b61ca05 100644 --- a/test/fixtures/default.txt +++ b/test/fixtures/default.txt @@ -35,20 +35,13 @@ aaa $$ bbb

aaa $$ bbb

. -Shouldn't render USD +Should require a closing delimiter . aaa $5.99 bbb .

aaa $5.99 bbb

. -Shouldn't render trailing delimiter -. -aaa 5.99$ bbb -. -

aaa 5.99$ bbb

-. - Paragraph break in inline math is not allowed . foo $1+1 @@ -59,14 +52,7 @@ foo $1+1

= 2$ bar

. -Neither is an end of document -. -foo $1+1 = 2 -. -

foo $1+1 = 2

-. - -Inline math with apparent markup +Inline math with apparent markup should not be processed . foo $1 *i* 1$ bar . @@ -201,16 +187,16 @@ Thus, $20,000 and USD$30,000 won't parse as math.

Thus, $20,000 and USD$30,000 won't parse as math.

. -Require non whitespace to left of opening inline math +Require non whitespace to right of opening inline math . For some Europeans, it is 2$ for a can of soda, not 1$. .

For some Europeans, it is 2$ for a can of soda, not 1$.

. -Require non whitespace to right of closing inline math. +Require non whitespace to left of closing inline math. . -I will give you 20$, if you give me 15$ tomorrow. +I will give you $20 today, if you give me more $ tomorrow. . -

I will give you 20$, if you give me 15$ tomorrow.

+

I will give you $20 today, if you give me more $ tomorrow.

.