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:
|
||||
|
||||
- `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:
|
||||
|
||||
|
|
|
@ -129,6 +129,14 @@ Parser.prototype.parseExpression = function(pos, mode, breakOnInfix, breakOnToke
|
|||
}
|
||||
var atom = this.parseAtom(pos, mode);
|
||||
if (!atom) {
|
||||
if (!this.settings.breakOnUnsupportedCmds && lex.text[0] === "\\") {
|
||||
var errorNode = this.handleUnsupportedCmd(lex.text, mode);
|
||||
body.push(errorNode);
|
||||
|
||||
pos = lex.position;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
if (breakOnInfix && atom.result.type === "infix") {
|
||||
|
@ -204,8 +212,16 @@ Parser.prototype.handleSupSubscript = function(pos, mode, symbol, name) {
|
|||
var group = this.parseGroup(pos, mode);
|
||||
|
||||
if (!group) {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + symbol + "'", this.lexer, pos);
|
||||
var lex = this.lexer.lex(pos, mode);
|
||||
|
||||
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) {
|
||||
// ^ and _ have a greediness, so handle interactions with functions'
|
||||
// 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.
|
||||
*
|
||||
|
@ -499,9 +546,18 @@ Parser.prototype.parseArguments = function(pos, mode, func, funcData, args) {
|
|||
arg = this.parseGroup(newPos, mode);
|
||||
}
|
||||
if (!arg) {
|
||||
throw new ParseError(
|
||||
"Expected group after '" + func + "'",
|
||||
this.lexer, newPos);
|
||||
var lex = this.lexer.lex(newPos, mode);
|
||||
|
||||
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;
|
||||
|
|
|
@ -21,6 +21,8 @@ function Settings(options) {
|
|||
// allow null options
|
||||
options = options || {};
|
||||
this.displayMode = get(options.displayMode, false);
|
||||
this.breakOnUnsupportedCmds = get(options.breakOnUnsupportedCmds, true);
|
||||
this.errorColor = get(options.errorColor, "#cc0000");
|
||||
}
|
||||
|
||||
module.exports = Settings;
|
||||
|
|
|
@ -54,7 +54,11 @@ var mathit = 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
|
||||
// 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);
|
||||
} else {
|
||||
return makeSymbol(
|
||||
|
|
|
@ -13,33 +13,39 @@ var Settings = require("../src/Settings");
|
|||
|
||||
var defaultSettings = new Settings({});
|
||||
|
||||
var getBuilt = function(expr) {
|
||||
expect(expr).toBuild();
|
||||
var getBuilt = function(expr, settings) {
|
||||
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
|
||||
return built.children[2].children;
|
||||
};
|
||||
|
||||
var getParsed = function(expr) {
|
||||
expect(expr).toParse();
|
||||
var getParsed = function(expr, settings) {
|
||||
var usedSettings = settings ? settings : defaultSettings;
|
||||
|
||||
return parseTree(expr, defaultSettings);
|
||||
expect(expr).toParse(usedSettings);
|
||||
return parseTree(expr, usedSettings);
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.addMatchers({
|
||||
toParse: function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
compare: function(actual, settings) {
|
||||
var usedSettings = settings ? settings : defaultSettings;
|
||||
|
||||
var result = {
|
||||
pass: true,
|
||||
message: "'" + actual + "' succeeded parsing"
|
||||
};
|
||||
|
||||
try {
|
||||
parseTree(actual, defaultSettings);
|
||||
parseTree(actual, usedSettings);
|
||||
} catch (e) {
|
||||
result.pass = false;
|
||||
if (e instanceof ParseError) {
|
||||
|
@ -58,7 +64,9 @@ beforeEach(function() {
|
|||
|
||||
toNotParse: function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
compare: function(actual, settings) {
|
||||
var usedSettings = settings ? settings : defaultSettings;
|
||||
|
||||
var result = {
|
||||
pass: false,
|
||||
message: "Expected '" + actual + "' to fail " +
|
||||
|
@ -66,7 +74,7 @@ beforeEach(function() {
|
|||
};
|
||||
|
||||
try {
|
||||
parseTree(actual, defaultSettings);
|
||||
parseTree(actual, usedSettings);
|
||||
} catch (e) {
|
||||
if (e instanceof ParseError) {
|
||||
result.pass = true;
|
||||
|
@ -85,16 +93,18 @@ beforeEach(function() {
|
|||
|
||||
toBuild: function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
compare: function(actual, settings) {
|
||||
var usedSettings = settings ? settings : defaultSettings;
|
||||
|
||||
var result = {
|
||||
pass: true,
|
||||
message: "'" + actual + "' succeeded in building"
|
||||
};
|
||||
|
||||
expect(actual).toParse();
|
||||
expect(actual).toParse(usedSettings);
|
||||
|
||||
try {
|
||||
buildHTML(parseTree(actual), defaultSettings);
|
||||
buildHTML(parseTree(actual, usedSettings), usedSettings);
|
||||
} catch (e) {
|
||||
result.pass = false;
|
||||
if (e instanceof ParseError) {
|
||||
|
@ -1359,10 +1369,12 @@ describe("A cases environment", function() {
|
|||
|
||||
});
|
||||
|
||||
var getMathML = function(expr) {
|
||||
expect(expr).toParse();
|
||||
var getMathML = function(expr, settings) {
|
||||
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>
|
||||
return built.children[0];
|
||||
|
@ -1400,3 +1412,45 @@ describe("A MathML builder", function() {
|
|||
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|",
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -25,9 +25,16 @@
|
|||
query[match[1]] = decodeURIComponent(match[2]);
|
||||
}
|
||||
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("post").innerHTML = query["post"] || "";
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue
Block a user