From aaf6947951789f1cd377ee4c1ba8b74105052ed6 Mon Sep 17 00:00:00 2001
From: cbreeden
Date: Thu, 12 May 2016 18:47:34 -0500
Subject: [PATCH 1/7] Updated KaTeX to 0.6.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index c30c185..cda3b41 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"author": "waylonflinn@gmail.com",
"license": "MIT",
"dependencies": {
- "katex": "^0.5.1"
+ "katex": "^0.6.0"
},
"devDependencies": {
"markdown-it": "^6.0.0",
From 3c4006c14fa2fb8f7693708d1728da47f5b08b6c Mon Sep 17 00:00:00 2001
From: cbreeden
Date: Thu, 12 May 2016 18:57:20 -0500
Subject: [PATCH 2/7] Updated Readme to include Syntax information
---
README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/README.md b/README.md
index 1b36e7b..49dcc91 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,17 @@ $$\begin{array}{c}
\end{array}$$
```
+## Syntax
+
+Math parsing in markdown is designed to agree with the conventions set by pandoc:
+
+ Anything between two $ characters will be treated as TeX math. The opening $ must
+ have a non-space character immediately to its right, while the closing $ must
+ have a non-space character immediately to its left, and must not be followed
+ immediately by a digit. Thus, $20,000 and $30,000 won’t parse as math. If for some
+ reason you need to enclose text in literal $ characters, backslash-escape them and
+ they won’t be treated as math delimiters.
+
## Math Syntax Support
KaTeX is based on TeX and LaTeX. Support for both is growing. Here's a list of
From a5a4427d2b91cd5e71bf23a2ce38e714b4dbfcfb Mon Sep 17 00:00:00 2001
From: cbreeden
Date: Thu, 12 May 2016 20:14:33 -0500
Subject: [PATCH 3/7] Add some failing tests
---
test/fixtures/default.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/test/fixtures/default.txt b/test/fixtures/default.txt
index 6c6e482..ef24ac3 100644
--- a/test/fixtures/default.txt
+++ b/test/fixtures/default.txt
@@ -193,3 +193,24 @@ $$
1
$$
.
+
+Numbers can not follow closing inline math
+.
+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
+.
+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.
+.
+I will give you 20$, if you give me 15$ tomorrow.
+.
+I will give you 20$, if you give me 15$ tomorrow.
+.
From 4349e4480e7b120d8938e980a0de4df57971695d Mon Sep 17 00:00:00 2001
From: cbreeden
Date: Fri, 13 May 2016 10:47:53 -0500
Subject: [PATCH 4/7] refactored inline processing for pandoc syntax
---
index.js | 273 ++++++++++++++++++++------------------
npm-debug.log | 23 ++++
test/fixtures/default.txt | 26 +---
3 files changed, 175 insertions(+), 147 deletions(-)
create mode 100644 npm-debug.log
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.
.
From 97cea23133fabccafbc79b991d9786afb80d65c6 Mon Sep 17 00:00:00 2001
From: Christopher Breeden
Date: Sun, 15 May 2016 13:10:37 -0500
Subject: [PATCH 5/7] Added failing test
---
test/fixtures/default.txt | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/test/fixtures/default.txt b/test/fixtures/default.txt
index b61ca05..3d6acc3 100644
--- a/test/fixtures/default.txt
+++ b/test/fixtures/default.txt
@@ -200,3 +200,10 @@ I will give you $20 today, if you give me more $ tomorrow.
.
I will give you $20 today, if you give me more $ tomorrow.
.
+
+Inline blockmath is not (currently) registered.
+.
+It's well know that $$1 + 1 = 3$$ for sufficiently large 1.
+.
+It's well know that $$1 + 1 = 3$$ for sufficiently large 1.
+.
From fd464f82dae0b427d80517ce7aa96dccb72b2f47 Mon Sep 17 00:00:00 2001
From: Christopher Breeden
Date: Sun, 15 May 2016 13:57:01 -0500
Subject: [PATCH 6/7] Added proper math-mode escaping
---
index.js | 45 ++++++++++++++++++++++-----------------
test/fixtures/default.txt | 14 ++++++++++++
2 files changed, 39 insertions(+), 20 deletions(-)
diff --git a/index.js b/index.js
index 64e6723..57e8eea 100644
--- a/index.js
+++ b/index.js
@@ -40,20 +40,33 @@ function isValidDelim(state, pos) {
}
function math_inline(state, silent) {
- var start, match, token, res,
- pos = state.pos;
+ var start, match, token, res, pos, esc_count;
if (state.src[state.pos] !== "$") { return false; }
- res = isValidDelim(state, pos);
+ res = isValidDelim(state, state.pos);
if (!res.can_open) {
if (!silent) { state.pending += "$"; }
state.pos += 1;
return true;
}
+ // First check for and bypass all properly escaped delimieters
+ // This loop will assume that the first leading backtick can not
+ // be the first character in state.src, which is known since
+ // we have found an opening delimieter already.
start = state.pos + 1;
- match = state.src.indexOf("$", start);
+ match = start;
+ while ( (match = state.src.indexOf("$", match)) !== -1) {
+ // Found potential $, look for escapes, pos will point to
+ // first non escape when complete
+ pos = match - 1;
+ while (state.src[pos] === "\\") { pos -= 1; }
+
+ // Even number of escapes, potential closing delimiter found
+ if ( ((match - pos) % 2) == 1 ) { break; }
+ match += 1;
+ }
// No closing delimter found. Consume $ and continue.
if (match === -1) {
@@ -62,22 +75,6 @@ function math_inline(state, silent) {
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 += "$$"; }
@@ -85,6 +82,14 @@ function math_inline(state, silent) {
return true;
}
+ // Check for valid closing delimiter
+ res = isValidDelim(state, match);
+ if (!res.can_close) {
+ if (!silent) { state.pending += "$"; }
+ state.pos = start;
+ return true;
+ }
+
if (!silent) {
token = state.push('math_inline', 'math', 0);
token.markup = "$";
diff --git a/test/fixtures/default.txt b/test/fixtures/default.txt
index 3d6acc3..bf9dc4e 100644
--- a/test/fixtures/default.txt
+++ b/test/fixtures/default.txt
@@ -207,3 +207,17 @@ It's well know that $$1 + 1 = 3$$ for sufficiently large 1.
.
It's well know that $$1 + 1 = 3$$ for sufficiently large 1.
.
+
+Escaped delimiters in math mode
+.
+Money adds: $\$X + \$Y = \$Z$.
+.
+Money adds: $X+$Y=$Z.
+.
+
+Multiple escaped delimiters in math module
+.
+Weird-o: $\displaystyle{\begin{pmatrix} \$ & 1\\\$ \end{pmatrix}}$.
+.
+Weird-o: ($$1).
+.
From a38d2cbd6369688d1c89c1e4c74592a6b7d14e1e Mon Sep 17 00:00:00 2001
From: Christopher Breeden
Date: Wed, 18 May 2016 18:23:28 -0500
Subject: [PATCH 7/7] delete npm-debug.log
---
npm-debug.log | 23 -----------------------
1 file changed, 23 deletions(-)
delete mode 100644 npm-debug.log
diff --git a/npm-debug.log b/npm-debug.log
deleted file mode 100644
index c3cf1a7..0000000
--- a/npm-debug.log
+++ /dev/null
@@ -1,23 +0,0 @@
-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 ]