Skip to content

Commit

Permalink
feat(auto-render): add option to ignore $ outside of LaTeX math mode (K…
Browse files Browse the repository at this point in the history
  • Loading branch information
lnasrc committed Nov 8, 2022
1 parent 3db8e34 commit 546dcf4
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 36 deletions.
5 changes: 4 additions & 1 deletion contrib/auto-render/auto-render.js
Expand Up @@ -7,7 +7,8 @@ import splitAtDelimiters from "./splitAtDelimiters";
* API, we should copy it before mutating.
*/
const renderMathInText = function(text, optionsCopy) {
const data = splitAtDelimiters(text, optionsCopy.delimiters);
const data = splitAtDelimiters(text, optionsCopy.delimiters,
optionsCopy.ignoreEscapedDollars);
if (data.length === 1 && data[0].type === 'text') {
// There is no formula in the text.
// Let's return null which means there is no need to replace
Expand Down Expand Up @@ -136,6 +137,8 @@ const renderMathInElement = function(elem, options) {
// math elements within a single call to `renderMathInElement`.
optionsCopy.macros = optionsCopy.macros || {};

optionsCopy.ignoreEscapedDollars = optionsCopy.ignoreEscapedDollars || false;

renderElem(elem, optionsCopy);
};

Expand Down
25 changes: 21 additions & 4 deletions contrib/auto-render/splitAtDelimiters.js
Expand Up @@ -27,18 +27,35 @@ const findEndOfMath = function(delimiter, text, startIndex) {
return -1;
};

const escapeRegex = function(string) {
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
const escapeRegex = function(string, ignoreEscapedDollars) {
if (ignoreEscapedDollars) {
if (string === "$") {
/* negative lookbehind to find any dollar not preceded by a
backslash */
// eslint-disable-next-line no-useless-escape
return "(?<!\\\\)\\\$";
} else if (string === "(") {
/* negative lookbehind to find any parenthesis not preceded by a
backslash */
// eslint-disable-next-line no-useless-escape
return "(?<!\\\\)\\\(";
} else {
return string.replace(/[-/\\^$*+?.)|[\]{}]/g, "\\$&");
}
} else {
return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
}
};

const amsRegex = /^\\begin{/;

const splitAtDelimiters = function(text, delimiters) {
const splitAtDelimiters = function(text, delimiters, ignoreEscapedDollars) {
let index;
const data = [];

const regexLeft = new RegExp(
"(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
"(" + delimiters.map((x) => escapeRegex(
x.left, ignoreEscapedDollars)).join("|") + ")"
);

while (true) {
Expand Down
107 changes: 76 additions & 31 deletions contrib/auto-render/test/auto-render-spec.js
Expand Up @@ -6,14 +6,14 @@ import renderMathInElement from "../auto-render";

beforeEach(function() {
expect.extend({
toSplitInto: function(actual, result, delimiters) {
toSplitInto: function(actual, result, delimiters, ignoreEscapedDollars) {
const message = {
pass: true,
message: () => "'" + actual + "' split correctly",
};

const split =
splitAtDelimiters(actual, delimiters);
splitAtDelimiters(actual, delimiters, ignoreEscapedDollars);

if (split.length !== result.length) {
message.pass = false;
Expand Down Expand Up @@ -65,7 +65,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("doesn't create a math node with only one left delimiter", function() {
Expand All @@ -76,7 +77,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("doesn't split when there's only a right delimiter", function() {
Expand All @@ -86,7 +88,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("splits when there are both delimiters", function() {
Expand All @@ -99,7 +102,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("splits on multi-character delimiters", function() {
Expand All @@ -112,7 +116,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "[[", right: "]]", display: false},
]);
],
/* ignoreEscapedDollars */ false);
expect("hello \\begin{equation} world \\end{equation} boo").toSplitInto(
[
{type: "text", data: "hello "},
Expand All @@ -124,7 +129,8 @@ describe("A delimiter splitter", function() {
[
{left: "\\begin{equation}", right: "\\end{equation}",
display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("splits mutliple times", function() {
Expand All @@ -140,7 +146,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("leaves the ending when there's only a left delimiter", function() {
Expand All @@ -154,7 +161,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("doesn't split when close delimiters are in {}s", function() {
Expand All @@ -167,7 +175,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);

expect("hello ( world { { } ) } ) boo").toSplitInto(
[
Expand All @@ -178,7 +187,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("correctly processes sequences of $..$", function() {
Expand All @@ -193,7 +203,8 @@ describe("A delimiter splitter", function() {
],
[
{left: "$", right: "$", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("doesn't split at escaped delimiters", function() {
Expand All @@ -206,18 +217,20 @@ describe("A delimiter splitter", function() {
],
[
{left: "(", right: ")", display: false},
]);
],
/* ignoreEscapedDollars */ false);

/* TODO(emily): make this work maybe?
expect("hello \\( ( world ) boo").toSplitInto(
"(", ")",
[
{type: "text", data: "hello \\( "},
{type: "math", data: " world ",
rawData: "( world )", display: false},
{type: "text", data: " boo"},
]);
*/
expect("hello \\( ( world ) boo").toSplitInto(
[
{type: "text", data: "hello \\( "},
{type: "math", data: " world ",
rawData: "( world )", display: false},
{type: "text", data: " boo"},
],
[
{left: "(", right: ")", display: false},
],
/* ignoreEscapedDollars */ true);
});

it("splits when the right and left delimiters are the same", function() {
Expand All @@ -230,7 +243,23 @@ describe("A delimiter splitter", function() {
],
[
{left: "$", right: "$", display: false},
]);
],
/* ignoreEscapedDollars */ false);
});

it("doesn't split at escaped delimiters in text mode", function() {
expect("I will give you 2\\$ if you can solve $y = x^{2}$ now.").
toSplitInto(
[
{type: "text", data: "I will give you 2\\$ if you can solve "},
{type: "math", data: "y = x^{2}",
rawData: "y = x^{2}", display: false},
{type: "text", data: " now."},
],
[
{left: "$", right: "$", display: false},
],
/* ignoreEscapedDollars */ true);
});

it("ignores \\$", function() {
Expand All @@ -241,14 +270,26 @@ describe("A delimiter splitter", function() {
],
[
{left: "$", right: "$", display: false},
]);
],
/* ignoreEscapedDollars */ false);

expect("$x = \\$5$").toSplitInto(
[
{type: "math", data: "x = \\$5",
rawData: "$x = \\$5$", display: false},
],
[
{left: "$", right: "$", display: false},
],
/* ignoreEscapedDollars */ true);
});

it("remembers which delimiters are display-mode", function() {
const startData = "hello ( world ) boo";

expect(splitAtDelimiters(startData,
[{left:"(", right:")", display:true}])).toEqual(
[{left:"(", right:")", display:true}],
/* ignoreEscapedDollars */ false)).toEqual(
[
{type: "text", data: "hello "},
{type: "math", data: " world ",
Expand All @@ -262,7 +303,8 @@ describe("A delimiter splitter", function() {
[
{left:"\\(", right:"\\)", display:false},
{left:"$", right:"$", display:false},
])).toEqual(
],
/* ignoreEscapedDollars */ false)).toEqual(
[
{type: "math", data: "\\fbox{\\(hi\\)}",
rawData: "$\\fbox{\\(hi\\)}$", display: false},
Expand All @@ -271,7 +313,8 @@ describe("A delimiter splitter", function() {
[
{left:"\\(", right:"\\)", display:false},
{left:"$", right:"$", display:false},
])).toEqual(
],
/* ignoreEscapedDollars */ false)).toEqual(
[
{type: "math", data: "\\fbox{$hi$}",
rawData: "\\(\\fbox{$hi$}\\)", display: false},
Expand All @@ -283,7 +326,8 @@ describe("A delimiter splitter", function() {
[
{left:"$$", right:"$$", display:true},
{left:"$", right:"$", display:false},
])).toEqual(
],
/* ignoreEscapedDollars */ false)).toEqual(
[
{type: "math", data: "hello",
rawData: "$hello$", display: false},
Expand All @@ -295,7 +339,8 @@ describe("A delimiter splitter", function() {
[
{left:"$$", right:"$$", display:true},
{left:"$", right:"$", display:false},
])).toEqual(
],
/* ignoreEscapedDollars */ false)).toEqual(
[
{type: "math", data: "hello",
rawData: "$hello$", display: false},
Expand Down
3 changes: 3 additions & 0 deletions docs/autorender.md
Expand Up @@ -128,6 +128,9 @@ in addition to five auto-render-specific keys:
- `preProcess`: A callback function, `(math: string) => string`, used to process
math expressions before rendering.

- `ignoreEscapedDollars`: `boolean` (default: `false`). If `true`, `\$` are ignored outside of LaTeX math expressions.
For example: `Please enjoy this 2\$ coffee and I'll explain why $e = mc^2$.`

The `displayMode` property of the options object is ignored, and is
instead taken from the `display` key of the corresponding entry in the
`delimiters` key.
Expand Down

0 comments on commit 546dcf4

Please sign in to comment.