Skip to content

Commit

Permalink
fix: MathML combines multidigit numbers with sup/subscript, comma sep…
Browse files Browse the repository at this point in the history
…arators, and multicharacter text when outputting to DOM (#3999)

* fix: MathML combines multidigit numbers and text when outputting to DOM

Fixes #3995

* Push numbers into sup/sub base, handle {,} separators

* Improve comments and merging efficiency
  • Loading branch information
edemaine authored Dec 17, 2024
1 parent 6e3fb74 commit 7d79e22
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 9 deletions.
48 changes: 42 additions & 6 deletions src/buildMathML.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ export const getVariant = function(
return null;
};

/**
* Check for <mi>.</mi> which is how a dot renders in MathML,
* or <mo separator="true" lspace="0em" rspace="0em">,</mo>
* which is how a braced comma {,} renders in MathML
*/
function isNumberPunctuation(group: ?MathNode): boolean {
if (!group) {
return false;
}
if (group.type === 'mi' && group.children.length === 1) {
const child = group.children[0];
return child instanceof TextNode && child.text === '.';
} else if (group.type === 'mo' && group.children.length === 1 &&
group.getAttribute('separator') === 'true' &&
group.getAttribute('lspace') === '0em' &&
group.getAttribute('rspace') === '0em'
) {
const child = group.children[0];
return child instanceof TextNode && child.text === ',';
} else {
return false;
}
}

/**
* Takes a list of nodes, builds them, and returns a list of the generated
* MathML nodes. Also combine consecutive <mtext> outputs into a single
Expand Down Expand Up @@ -165,13 +189,25 @@ export const buildExpression = function(
lastGroup.children.push(...group.children);
continue;
// Concatenate <mn>...</mn> followed by <mi>.</mi>
} else if (group.type === 'mi' && group.children.length === 1 &&
lastGroup.type === 'mn') {
const child = group.children[0];
if (child instanceof TextNode && child.text === '.') {
lastGroup.children.push(...group.children);
continue;
} else if (isNumberPunctuation(group) && lastGroup.type === 'mn') {
lastGroup.children.push(...group.children);
continue;
// Concatenate <mi>.</mi> followed by <mn>...</mn>
} else if (group.type === 'mn' && isNumberPunctuation(lastGroup)) {
group.children = [...lastGroup.children, ...group.children];
groups.pop();
// Put preceding <mn>...</mn> or <mi>.</mi> inside base of
// <msup><mn>...base...</mn>...exponent...</msup> (or <msub>)
} else if ((group.type === 'msup' || group.type === 'msub') &&
group.children.length >= 1 &&
(lastGroup.type === 'mn' || isNumberPunctuation(lastGroup))
) {
const base = group.children[0];
if (base instanceof MathNode && base.type === 'mn') {
base.children = [...lastGroup.children, ...base.children];
groups.pop();
}
// \not
} else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) {
const lastChild = lastGroup.children[0];
if (lastChild instanceof TextNode && lastChild.text === '\u0338' &&
Expand Down
13 changes: 12 additions & 1 deletion src/mathMLTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,18 @@ export class MathNode implements MathDomNode {
}

for (let i = 0; i < this.children.length; i++) {
node.appendChild(this.children[i].toNode());
// Combine multiple TextNodes into one TextNode, to prevent
// screen readers from reading each as a separate word [#3995]
if (this.children[i] instanceof TextNode &&
this.children[i + 1] instanceof TextNode) {
let text = this.children[i].toText() + this.children[++i].toText();
while (this.children[i + 1] instanceof TextNode) {
text += this.children[++i].toText();
}
node.appendChild(new TextNode(text).toNode());
} else {
node.appendChild(this.children[i].toNode());
}
}

return node;
Expand Down
28 changes: 27 additions & 1 deletion test/__snapshots__/mathml-spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,35 @@ exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
<mn>
0.34
</mn>
<mo>
=
</mo>
<msup>
<mn>
.34
</mn>
<mn>
1
</mn>
</msup>
</mrow>
<annotation encoding="application/x-tex">
\\sin{\\alpha}=0.34=.34^1
</annotation>
</semantics>
</math>
`;
exports[`A MathML builder should concatenate digits into single <mn> 2`] = `
<math xmlns="http://www.w3.org/1998/Math/MathML">
<semantics>
<mrow>
<mn>
1,000,000
</mn>
</mrow>
<annotation encoding="application/x-tex">
\\sin{\\alpha}=0.34
1{,}000{,}000
</annotation>
</semantics>
</math>
Expand Down
3 changes: 2 additions & 1 deletion test/mathml-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ describe("A MathML builder", function() {
});

it('should concatenate digits into single <mn>', () => {
expect(getMathML("\\sin{\\alpha}=0.34")).toMatchSnapshot();
expect(getMathML("\\sin{\\alpha}=0.34=.34^1")).toMatchSnapshot();
expect(getMathML("1{,}000{,}000")).toMatchSnapshot();
});

it('should make prime operators into <mo> nodes', () => {
Expand Down

0 comments on commit 7d79e22

Please sign in to comment.