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:
Erik Demaine
2018-05-21 22:56:34 -04:00
committed by GitHub
parent ef9cd5c172
commit 485c509879
8 changed files with 104 additions and 113 deletions

View File

@@ -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)) {

View File

@@ -261,9 +261,7 @@ defineFunction({
inner.push(rightNode);
}
const outerNode = new mathMLTree.MathNode("mrow", inner);
return outerNode;
return mml.makeRow(inner);
},
});

View File

@@ -243,9 +243,7 @@ defineFunction({
withDelims.push(rightOp);
}
const outerNode = new mathMLTree.MathNode("mrow", withDelims);
return outerNode;
return mml.makeRow(withDelims);
}
return node;

View File

@@ -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;
},

View File

@@ -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);
},
});

View File

@@ -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);
},
});