mirror of
https://github.com/Smaug123/KaTeX
synced 2025-10-05 11:18:39 +00:00
Cleanup MathML <mrow>, <mtext>, <mn> (#1338)
* Avoid unnecessary <mrow> wrapping buildMathML gains two helpers: * `makeRow` helper wraps an array of nodes in `<mrow>`, unless the array has length 1, in which case no wrapping is necessary. * `buildExpressionRow` for common case of `makeRow(buildExpression(...))` * Combine adjacent <mtext>s in all cases No more need for `makeTextRow` helper or anything fancy in text MathML handler. * Concatenate <mn>s and decimal point into single <mn> Fix #203 * Fix snapshots
This commit is contained in:
@@ -28,32 +28,15 @@ export const makeText = function(text, mode) {
|
||||
return new mathMLTree.TextNode(text);
|
||||
};
|
||||
|
||||
export const makeTextRow = function(body, options) {
|
||||
// Convert each element of the body into MathML, and combine consecutive
|
||||
// <mtext> outputs into a single <mtext> tag. In this way, we don't
|
||||
// nest non-text items (e.g., $nested-math$) within an <mtext>.
|
||||
const inner = [];
|
||||
let currentText = null;
|
||||
for (let i = 0; i < body.length; i++) {
|
||||
const group = buildGroup(body[i], options);
|
||||
if (group.type === 'mtext' && currentText !== null) {
|
||||
Array.prototype.push.apply(currentText.children, group.children);
|
||||
} else {
|
||||
inner.push(group);
|
||||
if (group.type === 'mtext') {
|
||||
currentText = group;
|
||||
} else {
|
||||
currentText = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a single tag in the end (presumably <mtext>),
|
||||
// just return it. Otherwise, wrap them in an <mrow>.
|
||||
if (inner.length === 1) {
|
||||
return inner[0];
|
||||
/**
|
||||
* Wrap the given array of nodes in an <mrow> node if needed, i.e.,
|
||||
* unless the array has length 1. Always returns a single node.
|
||||
*/
|
||||
export const makeRow = function(body) {
|
||||
if (body.length === 1) {
|
||||
return body[0];
|
||||
} else {
|
||||
return new mathMLTree.MathNode("mrow", inner);
|
||||
return new mathMLTree.MathNode("mrow", body);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,11 +80,7 @@ export const getVariant = function(group, options) {
|
||||
export const groupTypes = {};
|
||||
|
||||
groupTypes.ordgroup = function(group, options) {
|
||||
const inner = buildExpression(group.value, options);
|
||||
|
||||
const node = new mathMLTree.MathNode("mrow", inner);
|
||||
|
||||
return node;
|
||||
return buildExpressionRow(group.value, options);
|
||||
};
|
||||
|
||||
groupTypes.supsub = function(group, options) {
|
||||
@@ -119,18 +98,15 @@ groupTypes.supsub = function(group, options) {
|
||||
}
|
||||
}
|
||||
|
||||
const removeUnnecessaryRow = true;
|
||||
const children = [
|
||||
buildGroup(group.value.base, options, removeUnnecessaryRow)];
|
||||
buildGroup(group.value.base, options)];
|
||||
|
||||
if (group.value.sub) {
|
||||
children.push(
|
||||
buildGroup(group.value.sub, options, removeUnnecessaryRow));
|
||||
children.push(buildGroup(group.value.sub, options));
|
||||
}
|
||||
|
||||
if (group.value.sup) {
|
||||
children.push(
|
||||
buildGroup(group.value.sup, options, removeUnnecessaryRow));
|
||||
children.push(buildGroup(group.value.sup, options));
|
||||
}
|
||||
|
||||
let nodeType;
|
||||
@@ -167,11 +143,11 @@ groupTypes.supsub = function(group, options) {
|
||||
groupTypes.tag = function(group, options) {
|
||||
const table = new mathMLTree.MathNode("mtable", [
|
||||
new mathMLTree.MathNode("mlabeledtr", [
|
||||
new mathMLTree.MathNode("mtd",
|
||||
buildExpression(group.value.tag, options)),
|
||||
new mathMLTree.MathNode("mtd", [
|
||||
new mathMLTree.MathNode("mrow",
|
||||
buildExpression(group.value.body, options)),
|
||||
buildExpressionRow(group.value.tag, options),
|
||||
]),
|
||||
new mathMLTree.MathNode("mtd", [
|
||||
buildExpressionRow(group.value.body, options),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
@@ -181,14 +157,30 @@ groupTypes.tag = function(group, options) {
|
||||
|
||||
/**
|
||||
* Takes a list of nodes, builds them, and returns a list of the generated
|
||||
* MathML nodes. A little simpler than the HTML version because we don't do any
|
||||
* previous-node handling.
|
||||
* MathML nodes. Also combine consecutive <mtext> outputs into a single
|
||||
* <mtext> tag.
|
||||
*/
|
||||
export const buildExpression = function(expression, options) {
|
||||
const groups = [];
|
||||
let lastGroup;
|
||||
for (let i = 0; i < expression.length; i++) {
|
||||
const group = expression[i];
|
||||
groups.push(buildGroup(group, options));
|
||||
const group = buildGroup(expression[i], options);
|
||||
// Concatenate adjacent <mtext>s
|
||||
if (group.type === 'mtext' && lastGroup && lastGroup.type === 'mtext') {
|
||||
lastGroup.children.push(...group.children);
|
||||
// Concatenate adjacent <mn>s
|
||||
} else if (group.type === 'mn' &&
|
||||
lastGroup && lastGroup.type === 'mn') {
|
||||
lastGroup.children.push(...group.children);
|
||||
// Concatenate <mn>...</mn> followed by <mi>.</mi>
|
||||
} else if (group.type === 'mi' && group.children.length === 1 &&
|
||||
group.children[0].text === '.' &&
|
||||
lastGroup && lastGroup.type === 'mn') {
|
||||
lastGroup.children.push(...group.children);
|
||||
} else {
|
||||
groups.push(group);
|
||||
lastGroup = group;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kevinb): combine \\not with mrels and mords
|
||||
@@ -196,13 +188,19 @@ export const buildExpression = function(expression, options) {
|
||||
return groups;
|
||||
};
|
||||
|
||||
/**
|
||||
* Equivalent to buildExpression, but wraps the elements in an <mrow>
|
||||
* if there's more than one. Returns a single node instead of an array.
|
||||
*/
|
||||
export const buildExpressionRow = function(expression, options) {
|
||||
return makeRow(buildExpression(expression, options));
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a group from the parser and calls the appropriate groupTypes function
|
||||
* on it to produce a MathML node.
|
||||
*/
|
||||
export const buildGroup = function(
|
||||
group, options, removeUnnecessaryRow = false,
|
||||
) {
|
||||
export const buildGroup = function(group, options) {
|
||||
if (!group) {
|
||||
return new mathMLTree.MathNode("mrow");
|
||||
}
|
||||
@@ -210,11 +208,6 @@ export const buildGroup = function(
|
||||
if (groupTypes[group.type]) {
|
||||
// Call the groupTypes function
|
||||
const result = groupTypes[group.type](group, options);
|
||||
if (removeUnnecessaryRow) {
|
||||
if (result.type === "mrow" && result.children.length === 1) {
|
||||
return result.children[0];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new ParseError(
|
||||
@@ -234,7 +227,7 @@ export default function buildMathML(tree, texExpression, options) {
|
||||
const expression = buildExpression(tree, options);
|
||||
|
||||
// Wrap up the expression in an mrow so it is presented in the semantics
|
||||
// tag correctly.
|
||||
// tag correctly, unless it's a single <mrow> or <mtable>.
|
||||
let wrapper;
|
||||
if (expression.length === 1 &&
|
||||
utils.contains(["mrow", "mtable"], expression[0].type)) {
|
||||
|
@@ -261,9 +261,7 @@ defineFunction({
|
||||
inner.push(rightNode);
|
||||
}
|
||||
|
||||
const outerNode = new mathMLTree.MathNode("mrow", inner);
|
||||
|
||||
return outerNode;
|
||||
return mml.makeRow(inner);
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -243,9 +243,7 @@ defineFunction({
|
||||
withDelims.push(rightOp);
|
||||
}
|
||||
|
||||
const outerNode = new mathMLTree.MathNode("mrow", withDelims);
|
||||
|
||||
return outerNode;
|
||||
return mml.makeRow(withDelims);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import {assertNodeType} from "../ParseNode";
|
||||
|
||||
import * as html from "../buildHTML";
|
||||
@@ -35,8 +34,7 @@ defineFunction({
|
||||
return new buildCommon.makeAnchor(href, [], elements, options);
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const inner = mml.buildExpression(group.value.body, options);
|
||||
const math = new mathMLTree.MathNode("mrow", inner);
|
||||
const math = mml.buildExpressionRow(group.value.body, options);
|
||||
math.setAttribute("href", group.value.href);
|
||||
return math;
|
||||
},
|
||||
|
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
import defineFunction, {ordargument} from "../defineFunction";
|
||||
import buildCommon from "../buildCommon";
|
||||
import mathMLTree from "../mathMLTree";
|
||||
import Style from "../Style";
|
||||
import * as html from "../buildHTML";
|
||||
import * as mml from "../buildMathML";
|
||||
@@ -47,11 +46,6 @@ defineFunction({
|
||||
},
|
||||
mathmlBuilder: (group, options) => {
|
||||
const body = chooseMathStyle(group, options);
|
||||
const elements = mml.buildExpression(
|
||||
body,
|
||||
options,
|
||||
false
|
||||
);
|
||||
return new mathMLTree.MathNode("mrow", elements);
|
||||
return mml.buildExpressionRow(body, options);
|
||||
},
|
||||
});
|
||||
|
@@ -62,6 +62,6 @@ defineFunction({
|
||||
return buildCommon.makeSpan(["mord", "text"], inner, newOptions);
|
||||
},
|
||||
mathmlBuilder(group, options) {
|
||||
return mml.makeTextRow(group.value.body, options);
|
||||
return mml.buildExpressionRow(group.value.body, options);
|
||||
},
|
||||
});
|
||||
|
@@ -57,6 +57,35 @@ exports[`A MathML builder accents turn into <mover accent="true"> in MathML 1`]
|
||||
|
||||
`;
|
||||
|
||||
exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
|
||||
|
||||
<math>
|
||||
<semantics>
|
||||
<mrow>
|
||||
<mi>
|
||||
sin
|
||||
</mi>
|
||||
<mo>
|
||||
|
||||
</mo>
|
||||
<mi>
|
||||
α
|
||||
</mi>
|
||||
<mo>
|
||||
=
|
||||
</mo>
|
||||
<mn>
|
||||
0.34
|
||||
</mn>
|
||||
</mrow>
|
||||
<annotation encoding="application/x-tex">
|
||||
\\sin{\\alpha}=0.34
|
||||
</annotation>
|
||||
</semantics>
|
||||
</math>
|
||||
|
||||
`;
|
||||
|
||||
exports[`A MathML builder should generate <mphantom> nodes for \\phantom 1`] = `
|
||||
|
||||
<math>
|
||||
@@ -87,11 +116,9 @@ exports[`A MathML builder should generate the right types of nodes 1`] = `
|
||||
<mo>
|
||||
|
||||
</mo>
|
||||
<mrow>
|
||||
<mi>
|
||||
x
|
||||
</mi>
|
||||
</mrow>
|
||||
<mi>
|
||||
x
|
||||
</mi>
|
||||
<mo>
|
||||
+
|
||||
</mo>
|
||||
@@ -339,11 +366,9 @@ exports[`A MathML builder should render mathchoice as if there was nothing 3`] =
|
||||
<mi>
|
||||
x
|
||||
</mi>
|
||||
<mrow>
|
||||
<mi>
|
||||
T
|
||||
</mi>
|
||||
</mrow>
|
||||
<mi>
|
||||
T
|
||||
</mi>
|
||||
</msub>
|
||||
</mrow>
|
||||
<annotation encoding="application/x-tex">
|
||||
@@ -367,11 +392,9 @@ exports[`A MathML builder should render mathchoice as if there was nothing 4`] =
|
||||
<mi>
|
||||
y
|
||||
</mi>
|
||||
<mrow>
|
||||
<mi>
|
||||
T
|
||||
</mi>
|
||||
</mrow>
|
||||
<mi>
|
||||
T
|
||||
</mi>
|
||||
</msub>
|
||||
</msub>
|
||||
</mrow>
|
||||
@@ -387,8 +410,8 @@ exports[`A MathML builder should set href attribute for href appropriately 1`] =
|
||||
|
||||
<math>
|
||||
<semantics>
|
||||
<mrow href="http://example.org">
|
||||
<mi>
|
||||
<mrow>
|
||||
<mi href="http://example.org">
|
||||
α
|
||||
</mi>
|
||||
</mrow>
|
||||
@@ -406,11 +429,9 @@ exports[`A MathML builder should use <menclose> for colorbox 1`] = `
|
||||
<semantics>
|
||||
<mrow>
|
||||
<menclose mathbackground="red">
|
||||
<mrow>
|
||||
<mtext>
|
||||
b
|
||||
</mtext>
|
||||
</mrow>
|
||||
<mtext>
|
||||
b
|
||||
</mtext>
|
||||
</menclose>
|
||||
</mrow>
|
||||
<annotation encoding="application/x-tex">
|
||||
@@ -427,11 +448,9 @@ exports[`A MathML builder should use <mpadded> for raisebox 1`] = `
|
||||
<semantics>
|
||||
<mrow>
|
||||
<mpadded voffset="0.25em">
|
||||
<mrow>
|
||||
<mtext>
|
||||
b
|
||||
</mtext>
|
||||
</mrow>
|
||||
<mtext>
|
||||
b
|
||||
</mtext>
|
||||
</mpadded>
|
||||
</mrow>
|
||||
<annotation encoding="application/x-tex">
|
||||
@@ -507,22 +526,9 @@ exports[`A MathML builder tags use <mlabeledtr> 1`] = `
|
||||
<mtable side="right">
|
||||
<mlabeledtr>
|
||||
<mtd>
|
||||
<mrow>
|
||||
<mtext>
|
||||
(
|
||||
</mtext>
|
||||
<mrow>
|
||||
<mtext>
|
||||
h
|
||||
</mtext>
|
||||
<mtext>
|
||||
i
|
||||
</mtext>
|
||||
</mrow>
|
||||
<mtext>
|
||||
)
|
||||
</mtext>
|
||||
</mrow>
|
||||
<mtext>
|
||||
(hi)
|
||||
</mtext>
|
||||
</mtd>
|
||||
<mtd>
|
||||
<mrow>
|
||||
|
@@ -35,6 +35,10 @@ describe("A MathML builder", function() {
|
||||
expect(getMathML("\\sin{x}+1\\;\\text{a}")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should concatenate digits into single <mn>', () => {
|
||||
expect(getMathML("\\sin{\\alpha}=0.34")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should make prime operators into <mo> nodes', () => {
|
||||
expect(getMathML("f'")).toMatchSnapshot();
|
||||
});
|
||||
|
Reference in New Issue
Block a user