Skip to content

Commit eb32ae9

Browse files
authored
🧮 Use katex for MathML rendering in JATS (#507)
* 🧮 Use katex for MathML rendering in JATS * 🧮 Align MathML in JATS with other examples
1 parent 970576e commit eb32ae9

File tree

7 files changed

+45
-5
lines changed

7 files changed

+45
-5
lines changed

.changeset/good-apples-pump.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'myst-to-jats': patch
3+
'myst-cli': patch
4+
'mystmd': patch
5+
---
6+
7+
Add mathml to the exported JATS.

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/myst-to-jats/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"dependencies": {
3838
"citation-js-utils": "^1.0.1",
3939
"jats-tags": "^1.0.1",
40+
"katex": "^0.15.2",
4041
"myst-common": "^1.1.0",
4142
"myst-frontmatter": "^1.1.0",
4243
"myst-spec": "^0.0.4",
@@ -49,6 +50,7 @@
4950
},
5051
"devDependencies": {
5152
"@types/js-yaml": "^4.0.5",
53+
"@types/katex": "^0.14.0",
5254
"@types/mdast": "^3.0.10",
5355
"jats-xml": "^1.0.3",
5456
"js-yaml": "^4.1.0",

packages/myst-to-jats/src/index.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import type { Root, CrossReference, TableCell as SpecTableCell } from 'myst-spec
22
import type { Cite, Code, FootnoteDefinition, FootnoteReference } from 'myst-spec-ext';
33
import type { Plugin } from 'unified';
44
import type { VFile } from 'vfile';
5-
import { js2xml } from 'xml-js';
5+
import { js2xml, xml2js } from 'xml-js';
6+
import katex from 'katex';
67
import type { CitationRenderer } from 'citation-js-utils';
78
import type { MessageInfo, GenericNode } from 'myst-common';
89
import { copyNode, fileError } from 'myst-common';
@@ -118,6 +119,26 @@ function alternativesFromMinifiedOutput(output: MinifiedOutput, state: IJatsSeri
118119
state.closeNode();
119120
}
120121

122+
function mathToMml(math?: string, inline?: boolean) {
123+
const katexXml = katex.renderToString(math, { output: 'mathml', throwOnError: false });
124+
const katexJs = xml2js(katexXml, { compact: false }) as Element;
125+
const spanElement = katexJs.elements?.[0];
126+
const mathElement = spanElement?.elements?.[0];
127+
if (!mathElement) return;
128+
if (inline) mathElement.attributes = { ...mathElement.attributes, display: 'inline' };
129+
delete mathElement.attributes?.xmlns;
130+
function addMmlAndRemoveAnnotation(el?: Element) {
131+
if (el?.name) el.name = `mml:${el.name}`;
132+
if (!el?.elements) return;
133+
el.elements = el.elements.filter((child: Element) => child.name !== 'annotation');
134+
el.elements.forEach((child: Element) => {
135+
addMmlAndRemoveAnnotation(child);
136+
});
137+
}
138+
addMmlAndRemoveAnnotation(mathElement);
139+
return mathElement;
140+
}
141+
121142
const handlers: Record<string, Handler> = {
122143
text(node, state) {
123144
state.text(node.value);
@@ -181,10 +202,13 @@ const handlers: Record<string, Handler> = {
181202
},
182203
inlineMath(node, state) {
183204
state.openNode('inline-formula');
205+
state.openNode('alternatives');
206+
state.pushNode(mathToMml(node.value, true));
184207
state.openNode('tex-math');
185208
state.addLeaf('cdata', { cdata: node.value });
186209
state.closeNode();
187210
state.closeNode();
211+
state.closeNode();
188212
},
189213
math(node, state) {
190214
const dispFormulaAttrs: Attributes = {};
@@ -193,10 +217,13 @@ const handlers: Record<string, Handler> = {
193217
}
194218
state.openNode('disp-formula', dispFormulaAttrs);
195219
renderLabel(node, state, (enumerator) => `(${enumerator})`);
220+
state.openNode('alternatives');
221+
state.pushNode(mathToMml(node.value));
196222
state.openNode('tex-math');
197223
state.addLeaf('cdata', { cdata: node.value });
198224
state.closeNode();
199225
state.closeNode();
226+
state.closeNode();
200227
},
201228
mystRole(node, state) {
202229
state.renderChildren(node);

packages/myst-to-jats/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface IJatsSerializer<D extends Record<string, any> = StateData> {
5656
text: (value?: string) => void;
5757
renderChildren: (node: any) => void;
5858
renderInline: (node: GenericNode, name: string, attributes?: Attributes) => void;
59+
pushNode: (el?: Element) => void;
5960
addLeaf: (name: string, attributes?: Attributes) => void;
6061
openNode: (name: string, attributes?: Attributes) => void;
6162
closeNode: () => void;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'katex';

packages/myst-to-jats/tests/basic.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ cases:
189189
value: Ax=b
190190
- type: text
191191
value: .
192-
jats: <p>This math is a role, <inline-formula><tex-math><![CDATA[e=mc^2]]></tex-math></inline-formula>, while this math is wrapped in dollar signs, <inline-formula><tex-math><![CDATA[Ax=b]]></tex-math></inline-formula>.</p>
192+
jats: <p>This math is a role, <inline-formula><alternatives><mml:math display="inline"><mml:semantics><mml:mrow><mml:mi>e</mml:mi><mml:mo>=</mml:mo><mml:mi>m</mml:mi><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:semantics></mml:math><tex-math><![CDATA[e=mc^2]]></tex-math></alternatives></inline-formula>, while this math is wrapped in dollar signs, <inline-formula><alternatives><mml:math display="inline"><mml:semantics><mml:mrow><mml:mi>A</mml:mi><mml:mi>x</mml:mi><mml:mo>=</mml:mo><mml:mi>b</mml:mi></mml:mrow></mml:semantics></mml:math><tex-math><![CDATA[Ax=b]]></tex-math></alternatives></inline-formula>.</p>
193193
- title: Display Math (label)
194194
tree:
195195
type: root
@@ -198,14 +198,14 @@ cases:
198198
identifier: my-equation
199199
enumerator: 1
200200
value: w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}
201-
jats: <disp-formula id="my-equation"><label>(1)</label><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></disp-formula>
201+
jats: <disp-formula id="my-equation"><label>(1)</label><alternatives><mml:math><mml:semantics><mml:mrow><mml:msub><mml:mi>w</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo>=</mml:mo><mml:mo stretchy="false">(</mml:mo><mml:mn>1</mml:mn><mml:mo>+</mml:mo><mml:msub><mml:mi>r</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mi>s</mml:mi><mml:mo stretchy="false">(</mml:mo><mml:msub><mml:mi>w</mml:mi><mml:mi>t</mml:mi></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mo>+</mml:mo><mml:msub><mml:mi>y</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub></mml:mrow></mml:semantics></mml:math><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></alternatives></disp-formula>
202202
- title: Display Math (no label)
203203
tree:
204204
type: root
205205
children:
206206
- type: math
207207
value: w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}
208-
jats: <disp-formula><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></disp-formula>
208+
jats: <disp-formula><alternatives><mml:math><mml:semantics><mml:mrow><mml:msub><mml:mi>w</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo>=</mml:mo><mml:mo stretchy="false">(</mml:mo><mml:mn>1</mml:mn><mml:mo>+</mml:mo><mml:msub><mml:mi>r</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mi>s</mml:mi><mml:mo stretchy="false">(</mml:mo><mml:msub><mml:mi>w</mml:mi><mml:mi>t</mml:mi></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mo>+</mml:mo><mml:msub><mml:mi>y</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub></mml:mrow></mml:semantics></mml:math><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></alternatives></disp-formula>
209209
- title: Display Math (not numbered)
210210
tree:
211211
type: root
@@ -215,7 +215,7 @@ cases:
215215
enumerated: false
216216
enumerator: 1
217217
value: w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}
218-
jats: <disp-formula id="my-equation"><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></disp-formula>
218+
jats: <disp-formula id="my-equation"><alternatives><mml:math><mml:semantics><mml:mrow><mml:msub><mml:mi>w</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo>=</mml:mo><mml:mo stretchy="false">(</mml:mo><mml:mn>1</mml:mn><mml:mo>+</mml:mo><mml:msub><mml:mi>r</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mi>s</mml:mi><mml:mo stretchy="false">(</mml:mo><mml:msub><mml:mi>w</mml:mi><mml:mi>t</mml:mi></mml:msub><mml:mo stretchy="false">)</mml:mo><mml:mo>+</mml:mo><mml:msub><mml:mi>y</mml:mi><mml:mrow><mml:mi>t</mml:mi><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:mrow></mml:msub></mml:mrow></mml:semantics></mml:math><tex-math><![CDATA[w_{t+1} = (1 + r_{t+1}) s(w_t) + y_{t+1}]]></tex-math></alternatives></disp-formula>
219219
- title: Lists
220220
tree:
221221
type: root

0 commit comments

Comments
 (0)