Skip to content

Commit 7d79e22

Browse files
authored
fix: MathML combines multidigit numbers with sup/subscript, comma separators, 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
1 parent 6e3fb74 commit 7d79e22

File tree

4 files changed

+83
-9
lines changed

4 files changed

+83
-9
lines changed

src/buildMathML.js

+42-6
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,30 @@ export const getVariant = function(
128128
return null;
129129
};
130130

131+
/**
132+
* Check for <mi>.</mi> which is how a dot renders in MathML,
133+
* or <mo separator="true" lspace="0em" rspace="0em">,</mo>
134+
* which is how a braced comma {,} renders in MathML
135+
*/
136+
function isNumberPunctuation(group: ?MathNode): boolean {
137+
if (!group) {
138+
return false;
139+
}
140+
if (group.type === 'mi' && group.children.length === 1) {
141+
const child = group.children[0];
142+
return child instanceof TextNode && child.text === '.';
143+
} else if (group.type === 'mo' && group.children.length === 1 &&
144+
group.getAttribute('separator') === 'true' &&
145+
group.getAttribute('lspace') === '0em' &&
146+
group.getAttribute('rspace') === '0em'
147+
) {
148+
const child = group.children[0];
149+
return child instanceof TextNode && child.text === ',';
150+
} else {
151+
return false;
152+
}
153+
}
154+
131155
/**
132156
* Takes a list of nodes, builds them, and returns a list of the generated
133157
* MathML nodes. Also combine consecutive <mtext> outputs into a single
@@ -165,13 +189,25 @@ export const buildExpression = function(
165189
lastGroup.children.push(...group.children);
166190
continue;
167191
// Concatenate <mn>...</mn> followed by <mi>.</mi>
168-
} else if (group.type === 'mi' && group.children.length === 1 &&
169-
lastGroup.type === 'mn') {
170-
const child = group.children[0];
171-
if (child instanceof TextNode && child.text === '.') {
172-
lastGroup.children.push(...group.children);
173-
continue;
192+
} else if (isNumberPunctuation(group) && lastGroup.type === 'mn') {
193+
lastGroup.children.push(...group.children);
194+
continue;
195+
// Concatenate <mi>.</mi> followed by <mn>...</mn>
196+
} else if (group.type === 'mn' && isNumberPunctuation(lastGroup)) {
197+
group.children = [...lastGroup.children, ...group.children];
198+
groups.pop();
199+
// Put preceding <mn>...</mn> or <mi>.</mi> inside base of
200+
// <msup><mn>...base...</mn>...exponent...</msup> (or <msub>)
201+
} else if ((group.type === 'msup' || group.type === 'msub') &&
202+
group.children.length >= 1 &&
203+
(lastGroup.type === 'mn' || isNumberPunctuation(lastGroup))
204+
) {
205+
const base = group.children[0];
206+
if (base instanceof MathNode && base.type === 'mn') {
207+
base.children = [...lastGroup.children, ...base.children];
208+
groups.pop();
174209
}
210+
// \not
175211
} else if (lastGroup.type === 'mi' && lastGroup.children.length === 1) {
176212
const lastChild = lastGroup.children[0];
177213
if (lastChild instanceof TextNode && lastChild.text === '\u0338' &&

src/mathMLTree.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,18 @@ export class MathNode implements MathDomNode {
9595
}
9696

9797
for (let i = 0; i < this.children.length; i++) {
98-
node.appendChild(this.children[i].toNode());
98+
// Combine multiple TextNodes into one TextNode, to prevent
99+
// screen readers from reading each as a separate word [#3995]
100+
if (this.children[i] instanceof TextNode &&
101+
this.children[i + 1] instanceof TextNode) {
102+
let text = this.children[i].toText() + this.children[++i].toText();
103+
while (this.children[i + 1] instanceof TextNode) {
104+
text += this.children[++i].toText();
105+
}
106+
node.appendChild(new TextNode(text).toNode());
107+
} else {
108+
node.appendChild(this.children[i].toNode());
109+
}
99110
}
100111

101112
return node;

test/__snapshots__/mathml-spec.js.snap

+27-1
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,35 @@ exports[`A MathML builder should concatenate digits into single <mn> 1`] = `
428428
<mn>
429429
0.34
430430
</mn>
431+
<mo>
432+
=
433+
</mo>
434+
<msup>
435+
<mn>
436+
.34
437+
</mn>
438+
<mn>
439+
1
440+
</mn>
441+
</msup>
442+
</mrow>
443+
<annotation encoding="application/x-tex">
444+
\\sin{\\alpha}=0.34=.34^1
445+
</annotation>
446+
</semantics>
447+
</math>
448+
`;
449+
450+
exports[`A MathML builder should concatenate digits into single <mn> 2`] = `
451+
<math xmlns="http://www.w3.org/1998/Math/MathML">
452+
<semantics>
453+
<mrow>
454+
<mn>
455+
1,000,000
456+
</mn>
431457
</mrow>
432458
<annotation encoding="application/x-tex">
433-
\\sin{\\alpha}=0.34
459+
1{,}000{,}000
434460
</annotation>
435461
</semantics>
436462
</math>

test/mathml-spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ describe("A MathML builder", function() {
2929
});
3030

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

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

0 commit comments

Comments
 (0)