Merge pull request #317 from JeffEverett/unsupported_commands
Added support for visual depiction of unsupported commands
This commit is contained in:
commit
b2d2df9bef
|
@ -44,6 +44,9 @@ Make sure to include the CSS and font files, but there is no need to include the
|
||||||
You can provide an object of options as the last argument to `katex.render` and `katex.renderToString`. Available options are:
|
You can provide an object of options as the last argument to `katex.render` and `katex.renderToString`. Available options are:
|
||||||
|
|
||||||
- `displayMode`: `boolean`. If `true` the math will be rendered in display mode, which will put the math in display style (so `\int` and `\sum` are large, for example), and will center the math on the page on its own line. If `false` the math will be rendered in inline mode. (default: `false`)
|
- `displayMode`: `boolean`. If `true` the math will be rendered in display mode, which will put the math in display style (so `\int` and `\sum` are large, for example), and will center the math on the page on its own line. If `false` the math will be rendered in inline mode. (default: `false`)
|
||||||
|
- `breakOnUnsupportedCmds`: `boolean`. If `true`, KaTeX will generate a `ParseError` when it encounters an unsupported command. If `false`, KaTeX will render the command as text
|
||||||
|
in the color given by `errorColor`. (default: `true`)
|
||||||
|
- `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color which unsupported commands are rendered in. (default: `#cc0000`)
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,14 @@ Parser.prototype.parseExpression = function(pos, mode, breakOnInfix, breakOnToke
|
||||||
}
|
}
|
||||||
var atom = this.parseAtom(pos, mode);
|
var atom = this.parseAtom(pos, mode);
|
||||||
if (!atom) {
|
if (!atom) {
|
||||||
|
if (!this.settings.breakOnUnsupportedCmds && lex.text[0] === "\\") {
|
||||||
|
var errorNode = this.handleUnsupportedCmd(lex.text, mode);
|
||||||
|
body.push(errorNode);
|
||||||
|
|
||||||
|
pos = lex.position;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (breakOnInfix && atom.result.type === "infix") {
|
if (breakOnInfix && atom.result.type === "infix") {
|
||||||
|
@ -204,8 +212,16 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
||||||
var group = this.parseGroup(pos, mode);
|
var group = this.parseGroup(pos, mode);
|
||||||
|
|
||||||
if (!group) {
|
if (!group) {
|
||||||
throw new ParseError(
|
var lex = this.lexer.lex(pos, mode);
|
||||||
"Expected group after '" + symbol + "'", this.lexer, pos);
|
|
||||||
|
if (!this.settings.breakOnUnsupportedCmds && lex.text[0] === "\\") {
|
||||||
|
return new ParseResult(
|
||||||
|
this.handleUnsupportedCmd(lex.text, mode),
|
||||||
|
lex.position);
|
||||||
|
} else {
|
||||||
|
throw new ParseError(
|
||||||
|
"Expected group after '" + symbol + "'", this.lexer, pos);
|
||||||
|
}
|
||||||
} else if (group.isFunction) {
|
} else if (group.isFunction) {
|
||||||
// ^ and _ have a greediness, so handle interactions with functions'
|
// ^ and _ have a greediness, so handle interactions with functions'
|
||||||
// greediness
|
// greediness
|
||||||
|
@ -223,6 +239,37 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the textual input of an unsupported command into a text node
|
||||||
|
* contained within a color node whose color is determined by errorColor
|
||||||
|
*/
|
||||||
|
Parser.prototype.handleUnsupportedCmd = function(text, mode) {
|
||||||
|
var textordArray = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
textordArray.push(new ParseNode("textord", text[i], "text"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var textNode = new ParseNode(
|
||||||
|
"text",
|
||||||
|
{
|
||||||
|
body: textordArray,
|
||||||
|
type: "text"
|
||||||
|
},
|
||||||
|
mode);
|
||||||
|
|
||||||
|
var colorNode = new ParseNode(
|
||||||
|
"color",
|
||||||
|
{
|
||||||
|
color: this.settings.errorColor,
|
||||||
|
value: [textNode],
|
||||||
|
type: "color"
|
||||||
|
},
|
||||||
|
mode);
|
||||||
|
|
||||||
|
return colorNode;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a group with optional super/subscripts.
|
* Parses a group with optional super/subscripts.
|
||||||
*
|
*
|
||||||
|
@ -499,9 +546,18 @@ Parser.prototype.parseArguments = function(pos, mode, func, funcData, args) {
|
||||||
arg = this.parseGroup(newPos, mode);
|
arg = this.parseGroup(newPos, mode);
|
||||||
}
|
}
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
throw new ParseError(
|
var lex = this.lexer.lex(newPos, mode);
|
||||||
"Expected group after '" + func + "'",
|
|
||||||
this.lexer, newPos);
|
if (!this.settings.breakOnUnsupportedCmds && lex.text[0] === "\\") {
|
||||||
|
arg = new ParseFuncOrArgument(
|
||||||
|
new ParseResult(
|
||||||
|
this.handleUnsupportedCmd(lex.text, mode),
|
||||||
|
lex.position),
|
||||||
|
false);
|
||||||
|
} else {
|
||||||
|
throw new ParseError(
|
||||||
|
"Expected group after '" + func + "'", this.lexer, pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var argNode;
|
var argNode;
|
||||||
|
|
|
@ -21,6 +21,8 @@ function Settings(options) {
|
||||||
// allow null options
|
// allow null options
|
||||||
options = options || {};
|
options = options || {};
|
||||||
this.displayMode = get(options.displayMode, false);
|
this.displayMode = get(options.displayMode, false);
|
||||||
|
this.breakOnUnsupportedCmds = get(options.breakOnUnsupportedCmds, true);
|
||||||
|
this.errorColor = get(options.errorColor, "#cc0000");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Settings;
|
module.exports = Settings;
|
||||||
|
|
|
@ -54,7 +54,11 @@ var mathit = function(value, mode, color, classes) {
|
||||||
var mathrm = function(value, mode, color, classes) {
|
var mathrm = function(value, mode, color, classes) {
|
||||||
// Decide what font to render the symbol in by its entry in the symbols
|
// Decide what font to render the symbol in by its entry in the symbols
|
||||||
// table.
|
// table.
|
||||||
if (symbols[mode][value].font === "main") {
|
// Have a special case for when the value = \ because the \ is used as a
|
||||||
|
// textord in unsupported command errors but cannot be parsed as a regular
|
||||||
|
// text ordinal and is therefore not present as a symbol in the symbols
|
||||||
|
// table for text
|
||||||
|
if (value === "\\" || symbols[mode][value].font === "main") {
|
||||||
return makeSymbol(value, "Main-Regular", mode, color, classes);
|
return makeSymbol(value, "Main-Regular", mode, color, classes);
|
||||||
} else {
|
} else {
|
||||||
return makeSymbol(
|
return makeSymbol(
|
||||||
|
|
|
@ -13,33 +13,39 @@ var Settings = require("../src/Settings");
|
||||||
|
|
||||||
var defaultSettings = new Settings({});
|
var defaultSettings = new Settings({});
|
||||||
|
|
||||||
var getBuilt = function(expr) {
|
var getBuilt = function(expr, settings) {
|
||||||
expect(expr).toBuild();
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
var built = buildHTML(parseTree(expr), defaultSettings);
|
expect(expr).toBuild(usedSettings);
|
||||||
|
|
||||||
|
var parsedTree = parseTree(expr, usedSettings);
|
||||||
|
var built = buildHTML(parsedTree, usedSettings);
|
||||||
|
|
||||||
// Remove the outer .katex and .katex-inner layers
|
// Remove the outer .katex and .katex-inner layers
|
||||||
return built.children[2].children;
|
return built.children[2].children;
|
||||||
};
|
};
|
||||||
|
|
||||||
var getParsed = function(expr) {
|
var getParsed = function(expr, settings) {
|
||||||
expect(expr).toParse();
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
return parseTree(expr, defaultSettings);
|
expect(expr).toParse(usedSettings);
|
||||||
|
return parseTree(expr, usedSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
jasmine.addMatchers({
|
jasmine.addMatchers({
|
||||||
toParse: function() {
|
toParse: function() {
|
||||||
return {
|
return {
|
||||||
compare: function(actual) {
|
compare: function(actual, settings) {
|
||||||
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
pass: true,
|
pass: true,
|
||||||
message: "'" + actual + "' succeeded parsing"
|
message: "'" + actual + "' succeeded parsing"
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parseTree(actual, defaultSettings);
|
parseTree(actual, usedSettings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result.pass = false;
|
result.pass = false;
|
||||||
if (e instanceof ParseError) {
|
if (e instanceof ParseError) {
|
||||||
|
@ -58,7 +64,9 @@ beforeEach(function() {
|
||||||
|
|
||||||
toNotParse: function() {
|
toNotParse: function() {
|
||||||
return {
|
return {
|
||||||
compare: function(actual) {
|
compare: function(actual, settings) {
|
||||||
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
pass: false,
|
pass: false,
|
||||||
message: "Expected '" + actual + "' to fail " +
|
message: "Expected '" + actual + "' to fail " +
|
||||||
|
@ -66,7 +74,7 @@ beforeEach(function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parseTree(actual, defaultSettings);
|
parseTree(actual, usedSettings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ParseError) {
|
if (e instanceof ParseError) {
|
||||||
result.pass = true;
|
result.pass = true;
|
||||||
|
@ -85,16 +93,18 @@ beforeEach(function() {
|
||||||
|
|
||||||
toBuild: function() {
|
toBuild: function() {
|
||||||
return {
|
return {
|
||||||
compare: function(actual) {
|
compare: function(actual, settings) {
|
||||||
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
var result = {
|
var result = {
|
||||||
pass: true,
|
pass: true,
|
||||||
message: "'" + actual + "' succeeded in building"
|
message: "'" + actual + "' succeeded in building"
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(actual).toParse();
|
expect(actual).toParse(usedSettings);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
buildHTML(parseTree(actual), defaultSettings);
|
buildHTML(parseTree(actual, usedSettings), usedSettings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result.pass = false;
|
result.pass = false;
|
||||||
if (e instanceof ParseError) {
|
if (e instanceof ParseError) {
|
||||||
|
@ -1359,10 +1369,12 @@ describe("A cases environment", function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var getMathML = function(expr) {
|
var getMathML = function(expr, settings) {
|
||||||
expect(expr).toParse();
|
var usedSettings = settings ? settings : defaultSettings;
|
||||||
|
|
||||||
var built = buildMathML(parseTree(expr));
|
expect(expr).toParse(usedSettings);
|
||||||
|
|
||||||
|
var built = buildMathML(parseTree(expr, usedSettings), expr, usedSettings);
|
||||||
|
|
||||||
// Strip off the surrounding <span>
|
// Strip off the surrounding <span>
|
||||||
return built.children[0];
|
return built.children[0];
|
||||||
|
@ -1400,3 +1412,45 @@ describe("A MathML builder", function() {
|
||||||
expect(phantom.children[0].type).toEqual("mphantom");
|
expect(phantom.children[0].type).toEqual("mphantom");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("A parser that does not break on unsupported commands", function() {
|
||||||
|
// The parser breaks on unsupported commands unless it is explicitly
|
||||||
|
// told not to
|
||||||
|
var errorColor = "#933";
|
||||||
|
var doNotBreakSettings = new Settings({
|
||||||
|
breakOnUnsupportedCmds: false,
|
||||||
|
errorColor: errorColor
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still parse on unrecognized control sequences", function() {
|
||||||
|
expect("\\error").toParse(doNotBreakSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("should allow unrecognized controls sequences anywhere, including", function() {
|
||||||
|
it("in superscripts and subscripts", function() {
|
||||||
|
expect("2_\\error").toBuild(doNotBreakSettings);
|
||||||
|
expect("3^{\\error}_\\error").toBuild(doNotBreakSettings);
|
||||||
|
expect("\\int\\nolimits^\\error_\\error").toBuild(doNotBreakSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("in fractions", function() {
|
||||||
|
expect("\\frac{345}{\\error}").toBuild(doNotBreakSettings);
|
||||||
|
expect("\\frac\\error{\\error}").toBuild(doNotBreakSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("in square roots", function() {
|
||||||
|
expect("\\sqrt\\error").toBuild(doNotBreakSettings);
|
||||||
|
expect("\\sqrt{234\\error}").toBuild(doNotBreakSettings);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("in text boxes", function() {
|
||||||
|
expect("\\text{\\error}").toBuild(doNotBreakSettings);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should produce color nodes with a color value given by errorColor", function() {
|
||||||
|
var parsedInput = getParsed("\\error", doNotBreakSettings);
|
||||||
|
expect(parsedInput[0].type).toBe("color");
|
||||||
|
expect(parsedInput[0].value.color).toBe(errorColor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
BIN
test/screenshotter/images/UnsupportedCmds-chrome.png
Normal file
BIN
test/screenshotter/images/UnsupportedCmds-chrome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
test/screenshotter/images/UnsupportedCmds-firefox.png
Normal file
BIN
test/screenshotter/images/UnsupportedCmds-firefox.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -38,5 +38,6 @@
|
||||||
"SupSubHorizSpacing": "http://localhost:7936/test/screenshotter/test.html?m=x^{x^{x}}\\Big|x_{x_{x_{x_{x}}}}\\bigg|x^{x^{x_{x_{x_{x_{x}}}}}}\\bigg|",
|
"SupSubHorizSpacing": "http://localhost:7936/test/screenshotter/test.html?m=x^{x^{x}}\\Big|x_{x_{x_{x_{x}}}}\\bigg|x^{x^{x_{x_{x_{x_{x}}}}}}\\bigg|",
|
||||||
"SupSubOffsets": "http://localhost:7936/test/screenshotter/test.html?m=\\displaystyle \\int_{2+3}x f^{2+3}+3\\lim_{2+3+4+5}f",
|
"SupSubOffsets": "http://localhost:7936/test/screenshotter/test.html?m=\\displaystyle \\int_{2+3}x f^{2+3}+3\\lim_{2+3+4+5}f",
|
||||||
"Text": "http://localhost:7936/test/screenshotter/test.html?m=\\frac{a}{b}\\text{c~ {ab} \\ e}+fg",
|
"Text": "http://localhost:7936/test/screenshotter/test.html?m=\\frac{a}{b}\\text{c~ {ab} \\ e}+fg",
|
||||||
|
"UnsupportedCmds": "http://localhost:7936/test/screenshotter/test.html?m=\\err\\,\\frac\\fracerr3\\,2^\\superr_\\suberr\\,\\sqrt\\sqrterr&doNotBreak=1&errorColor=%23dd4c4c",
|
||||||
"VerticalSpacing": "http://localhost:7936/test/screenshotter/test.html?pre=potato<br>blah&post=<br>moo&m=x^{\\Huge y}z"
|
"VerticalSpacing": "http://localhost:7936/test/screenshotter/test.html?pre=potato<br>blah&post=<br>moo&m=x^{\\Huge y}z"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,16 @@
|
||||||
query[match[1]] = decodeURIComponent(match[2]);
|
query[match[1]] = decodeURIComponent(match[2]);
|
||||||
}
|
}
|
||||||
var mathNode = document.getElementById("math");
|
var mathNode = document.getElementById("math");
|
||||||
katex.render(query["m"], mathNode, {
|
|
||||||
displayMode: !!query["display"]
|
var settings = {
|
||||||
});
|
displayMode: !!query["display"],
|
||||||
|
breakOnUnsupportedCmds: !query["doNotBreak"]
|
||||||
|
};
|
||||||
|
if (query["errorColor"]) {
|
||||||
|
settings.errorColor = query["errorColor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
katex.render(query["m"], mathNode, settings);
|
||||||
document.getElementById("pre").innerHTML = query["pre"] || "";
|
document.getElementById("pre").innerHTML = query["pre"] || "";
|
||||||
document.getElementById("post").innerHTML = query["post"] || "";
|
document.getElementById("post").innerHTML = query["post"] || "";
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user