dwml-ts 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -178
- package/dist/index.d.ts +1 -1
- package/dist/index.js +114 -10
- package/dist/index.js.map +1 -1
- package/package.json +46 -37
package/README.md
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
# dwml-ts
|
|
2
|
-
|
|
3
|
-
Convert **MS Office OMML** (Office Math Markup Language, the `<m:oMath>` markup used
|
|
4
|
-
in `.docx` / `.pptx` / `.xlsx`) to **LaTeX** — in TypeScript.
|
|
5
|
-
|
|
6
|
-
A faithful port of the Python [`dwml`](https://github.com/xiilei/dwml) library with
|
|
7
|
-
**100% output fidelity** on its reference fixtures, hardened for real-world OOXML
|
|
8
|
-
import pipelines (PowerPoint, Word).
|
|
9
|
-
|
|
10
|
-
- **Pure, synchronous, deterministic** — no async, no config singletons.
|
|
11
|
-
- **DOM-free** — runs identically in the browser bundle and in Node. No
|
|
12
|
-
`DOMParser` / `XMLSerializer` / `xmldom`.
|
|
13
|
-
- **One tiny runtime dep**: [`txml`](https://github.com/TobiasNickel/tXml) for parsing
|
|
14
|
-
(which [PPTist](https://github.com/pipipi-pikachu/PPTist) already ships — zero new bytes).
|
|
15
|
-
- **Never throws on unknown constructs** — degrades per-node to concatenated text
|
|
16
|
-
content, so one odd `<m:oMath>` can't kill a whole paragraph import.
|
|
17
|
-
|
|
18
|
-
## Install
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install dwml-ts
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## API
|
|
25
|
-
|
|
26
|
-
All output is **bare LaTeX** — no `$`/`$$` delimiters, no trailing whitespace. The
|
|
27
|
-
caller decides inline vs. display math (e.g. PPTist wraps it in its own
|
|
28
|
-
`span.pptist-math[data-latex]` element).
|
|
29
|
-
|
|
30
|
-
### `ommlToLatex(xml, options?): string`
|
|
31
|
-
|
|
32
|
-
Convert one OMML fragment to bare LaTeX. Accepts `<m:oMath>`, `<m:oMathPara>` or the
|
|
33
|
-
`<a14:m>` wrapper as an XML string, and tolerates **missing `xmlns` declarations**
|
|
34
|
-
(fragments sliced out of slide XML where the namespace lives on a root you don't have).
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import { ommlToLatex } from 'dwml-ts';
|
|
38
|
-
|
|
39
|
-
ommlToLatex('<m:oMath><m:r><m:t>x+1</m:t></m:r></m:oMath>');
|
|
40
|
-
// => 'x+1'
|
|
41
|
-
|
|
42
|
-
ommlToLatex('<m:oMath><m:rad><m:radPr><m:degHide m:val="1"/></m:radPr><m:deg/><m:e><m:r><m:t>5</m:t></m:r></m:e></m:rad></m:oMath>');
|
|
43
|
-
// => '\\sqrt{5}'
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### `ommlNodeToLatex(node, options?): string`
|
|
47
|
-
|
|
48
|
-
Node-level entry: convert an **already-parsed txml `TNode`**. This is the important one
|
|
49
|
-
for import pipelines — walk each `<a:p>` of the slide XML yourself to preserve run order
|
|
50
|
-
(text run → math → text run), and pass math nodes straight in. One parse per slide, no
|
|
51
|
-
re-parsing fragments.
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import { parse } from 'txml';
|
|
55
|
-
import { ommlNodeToLatex } from 'dwml-ts';
|
|
56
|
-
|
|
57
|
-
const nodes = parse(slideXml);
|
|
58
|
-
// ... find the <m:oMath> node(s) you care about, then:
|
|
59
|
-
const latex = ommlNodeToLatex(omathNode);
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### `extractOmmlLatex(xml, options?): string[]`
|
|
63
|
-
|
|
64
|
-
Batch: find and convert every `<m:oMath>` in any OOXML document/fragment, returning one
|
|
65
|
-
bare LaTeX string per formula, in document order.
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { extractOmmlLatex } from 'dwml-ts';
|
|
69
|
-
|
|
70
|
-
const [first, second] = extractOmmlLatex(documentXml);
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### `OmmlToLatexOptions`
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
export interface OmmlToLatexOptions {
|
|
77
|
-
/** Called once per element local-name the converter had to degrade. */
|
|
78
|
-
onUnsupported?: (localName: string) => void;
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Behavior guarantees
|
|
83
|
-
|
|
84
|
-
| Guarantee | Notes |
|
|
85
|
-
| --- | --- |
|
|
86
|
-
| **Bare LaTeX out** | No delimiters, no trailing whitespace. |
|
|
87
|
-
| **Never throws** | Unknown constructs degrade per-node to concatenated text (`m:borderBox`, `m:phant`, unknown `m:fName`, unknown `m:limLow` base, …). A dropped `\frac` bar is recoverable; an exception is not. |
|
|
88
|
-
| **Namespace by local name** | Matches `oMath` / `f` / `r` / … regardless of prefix (`m:`, none, or re-prefixed). Real-world producers vary. |
|
|
89
|
-
| **Namespace-agnostic attributes** | `m:val` / `val` / re-prefixed attributes resolve by local name. |
|
|
90
|
-
|
|
91
|
-
## Supported OMML constructs
|
|
92
|
-
|
|
93
|
-
`m:oMath` / `m:oMathPara` / `<a14:m>` wrappers, and:
|
|
94
|
-
|
|
95
|
-
- **Runs** — `m:r` / `m:t` with Unicode→LaTeX symbol mapping (see below)
|
|
96
|
-
- **Fractions** — `m:f` with `m:fPr` types `bar` (default, `\frac`), `skw` (`^{n}/_{d}`),
|
|
97
|
-
`noBar` (`\genfrac`, binomial), `lin` (`{n}/{d}`)
|
|
98
|
-
- **Scripts** — `m:sSub`, `m:sSup`, `m:sSubSup` (and `m:sPre`, degraded)
|
|
99
|
-
- **Radicals** — `m:rad` with `m:deg` and `degHide` (`\sqrt`, `\sqrt[n]`)
|
|
100
|
-
- **N-ary** — `m:nary` (`\sum`, `\int`, `\prod`, … via `CHR_BO`)
|
|
101
|
-
- **Delimiters** — `m:d` with `begChr` / `endChr` / `sepChr` (`\left…\right`)
|
|
102
|
-
- **Functions** — `m:func` / `m:fName` (`\sin`, `\cos`, `\log`, `\ln`, …)
|
|
103
|
-
- **Accents** — `m:acc` (`\hat`, `\tilde`, `\vec`, … via `CHR`)
|
|
104
|
-
- **Bars** — `m:bar` (`\overline` / `\underline`)
|
|
105
|
-
- **Limits** — `m:limLow` (`\lim_{…}`), `m:limUpp` (`\overset`)
|
|
106
|
-
- **Matrix** — `m:m` / `m:mr` (`\begin{matrix}…\end{matrix}`)
|
|
107
|
-
- **Equation array** — `m:eqArr` (`\begin{array}{c}…\end{array}`)
|
|
108
|
-
- **Group char** — `m:groupChr` (`\overbrace`, `\underbrace`, …)
|
|
109
|
-
- **Box** — `m:box` (passthrough)
|
|
110
|
-
|
|
111
|
-
## Unicode → LaTeX mapping
|
|
112
|
-
|
|
113
|
-
Run text is mapped to LaTeX with a two-tier lookup (per Unicode code point, so
|
|
114
|
-
astral-plane Mathematical Alphanumeric Symbols are handled correctly):
|
|
115
|
-
|
|
116
|
-
1. **dwml's symbol table** (`src/latex-dict.ts`) — Mathematical Alphanumeric Symbols
|
|
117
|
-
`U+1D400–U+1D7FF` → ASCII (`𝐴→A`, `𝑥→x`), Greek (`π→\pi`, both plain `U+03B1..` and
|
|
118
|
-
math-italic `U+1D6FC..`), relation/operator symbols (`≤→\leq`, `≥→\geq`, `≠→\ne`,
|
|
119
|
-
`·→\cdot`, `×→\times`, `∞→\infty`, `→→\rightarrow`, …).
|
|
120
|
-
2. **pylatexenc's builtin map** (`src/uni2latex.generated.ts`, internalized from
|
|
121
|
-
pylatexenc 2.x, MIT) — everything else with a known LaTeX escape.
|
|
122
|
-
|
|
123
|
-
LaTeX-special characters in runs (`%`, `&`, `_`, `{`, `}`, `#`, `$`, `~`, `^`) are escaped
|
|
124
|
-
via dwml's `escape_latex` (`a%` → `a\%`).
|
|
125
|
-
|
|
126
|
-
## Examples
|
|
127
|
-
|
|
128
|
-
| OMML input | LaTeX output |
|
|
129
|
-
| --- | --- |
|
|
130
|
-
| `\sin(\sqrt[3]{x})^{x^{11}}/_{b}x_{m}^{n}` fixture | `\sin(\sqrt[3]{x})^{x^{11}}/_{b}x_{m}^{n}` |
|
|
131
|
-
| group fixture | `A\overbrace{123}\underbrace{456}=\left\{a+b\right)` |
|
|
132
|
-
| matrix fixture | `A=\left(\begin{matrix}1&2&3\\4&5&6\end{matrix}\right)\sum_{1}^{20}x` |
|
|
133
|
-
| no-bar fraction (binomial) | `\left(x+a\right)^{n}=\sum_{k=0}^{n}\left(\genfrac{}{}{0pt}{}{n}{k}\right)x^{k}a^{n-k}` |
|
|
134
|
-
|
|
135
|
-
## Project layout
|
|
136
|
-
|
|
137
|
-
```
|
|
138
|
-
src/
|
|
139
|
-
index.ts public API (ommlToLatex / ommlNodeToLatex / extractOmmlLatex)
|
|
140
|
-
omml.ts the converter (never-throw, namespace-agnostic)
|
|
141
|
-
latex-dict.ts dwml symbol dictionaries
|
|
142
|
-
latex-encode.ts pylatexenc-derived unicode->latex encoder
|
|
143
|
-
uni2latex.generated.ts generated pylatexenc map (do not edit by hand)
|
|
144
|
-
tests/
|
|
145
|
-
api.test.ts consumer contract + dwml reference fidelity
|
|
146
|
-
constructs.test.ts per-construct coverage
|
|
147
|
-
ground-truth.json Python dwml reference outputs
|
|
148
|
-
reference/ original Python sources (base + two forks + canonical merge)
|
|
149
|
-
scripts/
|
|
150
|
-
gen_uni2latex.py regenerate the unicode map from pylatexenc
|
|
151
|
-
gen_ground_truth.py regenerate tests/ground-truth.json from Python dwml
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Development
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
npm install
|
|
158
|
-
npm test # rstest
|
|
159
|
-
npm run build # rslib -> dist/{index.js,index.d.ts} (ESM only)
|
|
160
|
-
|
|
161
|
-
# regenerate the unicode map / ground truth (requires Python + pylatexenc)
|
|
162
|
-
npm run gen:unicode
|
|
163
|
-
npm run gen:groundtruth
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## Attribution
|
|
167
|
-
|
|
168
|
-
- Port of [`dwml`](https://github.com/xiilei/dwml) (Apache-2.0), incorporating fixes from
|
|
169
|
-
the [`xavier-bw/dwml`](https://github.com/xavier-bw/dwml) fork (log/ln functions) and
|
|
170
|
-
[`bazingarj/OMML-to-Latex-py`](https://github.com/bazingarj/OMML-to-Latex-py) fork
|
|
171
|
-
(whitespace-tolerant function names). Test fixtures under `reference/tests-base/` are
|
|
172
|
-
Apache-2.0, from the original project.
|
|
173
|
-
- The Unicode→LaTeX map is derived from [`pylatexenc`](https://github.com/phfaist/pylatexenc)
|
|
174
|
-
(MIT, © Philippe Faist).
|
|
175
|
-
|
|
176
|
-
## License
|
|
177
|
-
|
|
178
|
-
Apache-2.0
|
|
1
|
+
# dwml-ts
|
|
2
|
+
|
|
3
|
+
Convert **MS Office OMML** (Office Math Markup Language, the `<m:oMath>` markup used
|
|
4
|
+
in `.docx` / `.pptx` / `.xlsx`) to **LaTeX** — in TypeScript.
|
|
5
|
+
|
|
6
|
+
A faithful port of the Python [`dwml`](https://github.com/xiilei/dwml) library with
|
|
7
|
+
**100% output fidelity** on its reference fixtures, hardened for real-world OOXML
|
|
8
|
+
import pipelines (PowerPoint, Word).
|
|
9
|
+
|
|
10
|
+
- **Pure, synchronous, deterministic** — no async, no config singletons.
|
|
11
|
+
- **DOM-free** — runs identically in the browser bundle and in Node. No
|
|
12
|
+
`DOMParser` / `XMLSerializer` / `xmldom`.
|
|
13
|
+
- **One tiny runtime dep**: [`txml`](https://github.com/TobiasNickel/tXml) for parsing
|
|
14
|
+
(which [PPTist](https://github.com/pipipi-pikachu/PPTist) already ships — zero new bytes).
|
|
15
|
+
- **Never throws on unknown constructs** — degrades per-node to concatenated text
|
|
16
|
+
content, so one odd `<m:oMath>` can't kill a whole paragraph import.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install dwml-ts
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
All output is **bare LaTeX** — no `$`/`$$` delimiters, no trailing whitespace. The
|
|
27
|
+
caller decides inline vs. display math (e.g. PPTist wraps it in its own
|
|
28
|
+
`span.pptist-math[data-latex]` element).
|
|
29
|
+
|
|
30
|
+
### `ommlToLatex(xml, options?): string`
|
|
31
|
+
|
|
32
|
+
Convert one OMML fragment to bare LaTeX. Accepts `<m:oMath>`, `<m:oMathPara>` or the
|
|
33
|
+
`<a14:m>` wrapper as an XML string, and tolerates **missing `xmlns` declarations**
|
|
34
|
+
(fragments sliced out of slide XML where the namespace lives on a root you don't have).
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { ommlToLatex } from 'dwml-ts';
|
|
38
|
+
|
|
39
|
+
ommlToLatex('<m:oMath><m:r><m:t>x+1</m:t></m:r></m:oMath>');
|
|
40
|
+
// => 'x+1'
|
|
41
|
+
|
|
42
|
+
ommlToLatex('<m:oMath><m:rad><m:radPr><m:degHide m:val="1"/></m:radPr><m:deg/><m:e><m:r><m:t>5</m:t></m:r></m:e></m:rad></m:oMath>');
|
|
43
|
+
// => '\\sqrt{5}'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### `ommlNodeToLatex(node, options?): string`
|
|
47
|
+
|
|
48
|
+
Node-level entry: convert an **already-parsed txml `TNode`**. This is the important one
|
|
49
|
+
for import pipelines — walk each `<a:p>` of the slide XML yourself to preserve run order
|
|
50
|
+
(text run → math → text run), and pass math nodes straight in. One parse per slide, no
|
|
51
|
+
re-parsing fragments.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { parse } from 'txml';
|
|
55
|
+
import { ommlNodeToLatex } from 'dwml-ts';
|
|
56
|
+
|
|
57
|
+
const nodes = parse(slideXml);
|
|
58
|
+
// ... find the <m:oMath> node(s) you care about, then:
|
|
59
|
+
const latex = ommlNodeToLatex(omathNode);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `extractOmmlLatex(xml, options?): string[]`
|
|
63
|
+
|
|
64
|
+
Batch: find and convert every `<m:oMath>` in any OOXML document/fragment, returning one
|
|
65
|
+
bare LaTeX string per formula, in document order.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { extractOmmlLatex } from 'dwml-ts';
|
|
69
|
+
|
|
70
|
+
const [first, second] = extractOmmlLatex(documentXml);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `OmmlToLatexOptions`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
export interface OmmlToLatexOptions {
|
|
77
|
+
/** Called once per element local-name the converter had to degrade. */
|
|
78
|
+
onUnsupported?: (localName: string) => void;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Behavior guarantees
|
|
83
|
+
|
|
84
|
+
| Guarantee | Notes |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| **Bare LaTeX out** | No delimiters, no trailing whitespace. |
|
|
87
|
+
| **Never throws** | Unknown constructs degrade per-node to concatenated text (`m:borderBox`, `m:phant`, unknown `m:fName`, unknown `m:limLow` base, …). A dropped `\frac` bar is recoverable; an exception is not. |
|
|
88
|
+
| **Namespace by local name** | Matches `oMath` / `f` / `r` / … regardless of prefix (`m:`, none, or re-prefixed). Real-world producers vary. |
|
|
89
|
+
| **Namespace-agnostic attributes** | `m:val` / `val` / re-prefixed attributes resolve by local name. |
|
|
90
|
+
|
|
91
|
+
## Supported OMML constructs
|
|
92
|
+
|
|
93
|
+
`m:oMath` / `m:oMathPara` / `<a14:m>` wrappers, and:
|
|
94
|
+
|
|
95
|
+
- **Runs** — `m:r` / `m:t` with Unicode→LaTeX symbol mapping (see below)
|
|
96
|
+
- **Fractions** — `m:f` with `m:fPr` types `bar` (default, `\frac`), `skw` (`^{n}/_{d}`),
|
|
97
|
+
`noBar` (`\genfrac`, binomial), `lin` (`{n}/{d}`)
|
|
98
|
+
- **Scripts** — `m:sSub`, `m:sSup`, `m:sSubSup` (and `m:sPre`, degraded)
|
|
99
|
+
- **Radicals** — `m:rad` with `m:deg` and `degHide` (`\sqrt`, `\sqrt[n]`)
|
|
100
|
+
- **N-ary** — `m:nary` (`\sum`, `\int`, `\prod`, … via `CHR_BO`)
|
|
101
|
+
- **Delimiters** — `m:d` with `begChr` / `endChr` / `sepChr` (`\left…\right`)
|
|
102
|
+
- **Functions** — `m:func` / `m:fName` (`\sin`, `\cos`, `\log`, `\ln`, …)
|
|
103
|
+
- **Accents** — `m:acc` (`\hat`, `\tilde`, `\vec`, … via `CHR`)
|
|
104
|
+
- **Bars** — `m:bar` (`\overline` / `\underline`)
|
|
105
|
+
- **Limits** — `m:limLow` (`\lim_{…}`), `m:limUpp` (`\overset`)
|
|
106
|
+
- **Matrix** — `m:m` / `m:mr` (`\begin{matrix}…\end{matrix}`)
|
|
107
|
+
- **Equation array** — `m:eqArr` (`\begin{array}{c}…\end{array}`)
|
|
108
|
+
- **Group char** — `m:groupChr` (`\overbrace`, `\underbrace`, …)
|
|
109
|
+
- **Box** — `m:box` (passthrough)
|
|
110
|
+
|
|
111
|
+
## Unicode → LaTeX mapping
|
|
112
|
+
|
|
113
|
+
Run text is mapped to LaTeX with a two-tier lookup (per Unicode code point, so
|
|
114
|
+
astral-plane Mathematical Alphanumeric Symbols are handled correctly):
|
|
115
|
+
|
|
116
|
+
1. **dwml's symbol table** (`src/latex-dict.ts`) — Mathematical Alphanumeric Symbols
|
|
117
|
+
`U+1D400–U+1D7FF` → ASCII (`𝐴→A`, `𝑥→x`), Greek (`π→\pi`, both plain `U+03B1..` and
|
|
118
|
+
math-italic `U+1D6FC..`), relation/operator symbols (`≤→\leq`, `≥→\geq`, `≠→\ne`,
|
|
119
|
+
`·→\cdot`, `×→\times`, `∞→\infty`, `→→\rightarrow`, …).
|
|
120
|
+
2. **pylatexenc's builtin map** (`src/uni2latex.generated.ts`, internalized from
|
|
121
|
+
pylatexenc 2.x, MIT) — everything else with a known LaTeX escape.
|
|
122
|
+
|
|
123
|
+
LaTeX-special characters in runs (`%`, `&`, `_`, `{`, `}`, `#`, `$`, `~`, `^`) are escaped
|
|
124
|
+
via dwml's `escape_latex` (`a%` → `a\%`).
|
|
125
|
+
|
|
126
|
+
## Examples
|
|
127
|
+
|
|
128
|
+
| OMML input | LaTeX output |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `\sin(\sqrt[3]{x})^{x^{11}}/_{b}x_{m}^{n}` fixture | `\sin(\sqrt[3]{x})^{x^{11}}/_{b}x_{m}^{n}` |
|
|
131
|
+
| group fixture | `A\overbrace{123}\underbrace{456}=\left\{a+b\right)` |
|
|
132
|
+
| matrix fixture | `A=\left(\begin{matrix}1&2&3\\4&5&6\end{matrix}\right)\sum_{1}^{20}x` |
|
|
133
|
+
| no-bar fraction (binomial) | `\left(x+a\right)^{n}=\sum_{k=0}^{n}\left(\genfrac{}{}{0pt}{}{n}{k}\right)x^{k}a^{n-k}` |
|
|
134
|
+
|
|
135
|
+
## Project layout
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
src/
|
|
139
|
+
index.ts public API (ommlToLatex / ommlNodeToLatex / extractOmmlLatex)
|
|
140
|
+
omml.ts the converter (never-throw, namespace-agnostic)
|
|
141
|
+
latex-dict.ts dwml symbol dictionaries
|
|
142
|
+
latex-encode.ts pylatexenc-derived unicode->latex encoder
|
|
143
|
+
uni2latex.generated.ts generated pylatexenc map (do not edit by hand)
|
|
144
|
+
tests/
|
|
145
|
+
api.test.ts consumer contract + dwml reference fidelity
|
|
146
|
+
constructs.test.ts per-construct coverage
|
|
147
|
+
ground-truth.json Python dwml reference outputs
|
|
148
|
+
reference/ original Python sources (base + two forks + canonical merge)
|
|
149
|
+
scripts/
|
|
150
|
+
gen_uni2latex.py regenerate the unicode map from pylatexenc
|
|
151
|
+
gen_ground_truth.py regenerate tests/ground-truth.json from Python dwml
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm install
|
|
158
|
+
npm test # rstest
|
|
159
|
+
npm run build # rslib -> dist/{index.js,index.d.ts} (ESM only)
|
|
160
|
+
|
|
161
|
+
# regenerate the unicode map / ground truth (requires Python + pylatexenc)
|
|
162
|
+
npm run gen:unicode
|
|
163
|
+
npm run gen:groundtruth
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Attribution
|
|
167
|
+
|
|
168
|
+
- Port of [`dwml`](https://github.com/xiilei/dwml) (Apache-2.0), incorporating fixes from
|
|
169
|
+
the [`xavier-bw/dwml`](https://github.com/xavier-bw/dwml) fork (log/ln functions) and
|
|
170
|
+
[`bazingarj/OMML-to-Latex-py`](https://github.com/bazingarj/OMML-to-Latex-py) fork
|
|
171
|
+
(whitespace-tolerant function names). Test fixtures under `reference/tests-base/` are
|
|
172
|
+
Apache-2.0, from the original project.
|
|
173
|
+
- The Unicode→LaTeX map is derived from [`pylatexenc`](https://github.com/phfaist/pylatexenc)
|
|
174
|
+
(MIT, © Philippe Faist).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
Apache-2.0
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parse } from "txml";
|
|
1
|
+
import { parse } from "txml/txml";
|
|
2
2
|
const UNI2LATEX = {
|
|
3
3
|
34: "''",
|
|
4
4
|
35: "\\#",
|
|
@@ -1808,7 +1808,32 @@ const T = {
|
|
|
1808
1808
|
𝑤: 'w',
|
|
1809
1809
|
𝑥: 'x',
|
|
1810
1810
|
𝑦: 'y',
|
|
1811
|
-
𝑧: 'z'
|
|
1811
|
+
𝑧: 'z',
|
|
1812
|
+
𝒉: 'h',
|
|
1813
|
+
𝛢: 'A',
|
|
1814
|
+
𝛣: 'B',
|
|
1815
|
+
𝛤: '\\Gamma ',
|
|
1816
|
+
𝛥: '\\Delta ',
|
|
1817
|
+
𝛦: 'E',
|
|
1818
|
+
𝛧: 'Z',
|
|
1819
|
+
𝛨: 'H',
|
|
1820
|
+
𝛩: '\\Theta ',
|
|
1821
|
+
𝛪: 'I',
|
|
1822
|
+
𝛫: 'K',
|
|
1823
|
+
𝛬: '\\Lambda ',
|
|
1824
|
+
𝛭: 'M',
|
|
1825
|
+
𝛮: 'N',
|
|
1826
|
+
𝛯: '\\Xi ',
|
|
1827
|
+
𝛰: 'O',
|
|
1828
|
+
𝛱: '\\Pi ',
|
|
1829
|
+
𝛲: 'P',
|
|
1830
|
+
𝛴: '\\Sigma ',
|
|
1831
|
+
𝛵: 'T',
|
|
1832
|
+
𝛶: '\\Upsilon ',
|
|
1833
|
+
𝛷: '\\Phi ',
|
|
1834
|
+
𝛸: 'X',
|
|
1835
|
+
𝛹: '\\Psi ',
|
|
1836
|
+
𝛺: '\\Omega '
|
|
1812
1837
|
};
|
|
1813
1838
|
const FUNC = {
|
|
1814
1839
|
sin: '\\sin({fe})',
|
|
@@ -1866,12 +1891,21 @@ const F = {
|
|
|
1866
1891
|
lin: '{{{num}}}/{{{den}}}'
|
|
1867
1892
|
};
|
|
1868
1893
|
const F_DEFAULT = '\\frac{{{num}}}{{{den}}}';
|
|
1894
|
+
const F_SMALL = '\\tfrac{{{num}}}{{{den}}}';
|
|
1869
1895
|
const D = '\\left{left}{text}\\right{right}';
|
|
1870
1896
|
const D_DEFAULT = {
|
|
1871
1897
|
left: '(',
|
|
1872
1898
|
right: ')',
|
|
1899
|
+
sep: '|',
|
|
1873
1900
|
null: '.'
|
|
1874
1901
|
};
|
|
1902
|
+
const BORDER_BOX = '\\boxed{{{0}}}';
|
|
1903
|
+
const PHANT = '\\phantom{{{0}}}';
|
|
1904
|
+
const M_JC = {
|
|
1905
|
+
left: '\\begin{{array}}{{l}}{text}\\end{{array}}',
|
|
1906
|
+
right: '\\begin{{array}}{{r}}{text}\\end{{array}}',
|
|
1907
|
+
center: '\\begin{{matrix}}{text}\\end{{matrix}}'
|
|
1908
|
+
};
|
|
1875
1909
|
const RAD = '\\sqrt[{deg}]{{{text}}}';
|
|
1876
1910
|
const RAD_DEFAULT = '\\sqrt{{{text}}}';
|
|
1877
1911
|
const ARR = '\\begin{{array}}{{c}}{text}\\end{{array}}';
|
|
@@ -1967,12 +2001,25 @@ function getVal(key, def, store) {
|
|
|
1967
2001
|
}
|
|
1968
2002
|
return def;
|
|
1969
2003
|
}
|
|
2004
|
+
function isOn(val, def = false) {
|
|
2005
|
+
if (void 0 === val) return def;
|
|
2006
|
+
const v = val.toLowerCase();
|
|
2007
|
+
return 'on' === v || '1' === v || 'true' === v;
|
|
2008
|
+
}
|
|
1970
2009
|
const PR_VAL_TAGS = new Set([
|
|
1971
2010
|
'chr',
|
|
1972
2011
|
'pos',
|
|
1973
2012
|
'begChr',
|
|
1974
2013
|
'endChr',
|
|
1975
|
-
'
|
|
2014
|
+
'sepChr',
|
|
2015
|
+
'type',
|
|
2016
|
+
'show',
|
|
2017
|
+
'smallFrac',
|
|
2018
|
+
'limLoc',
|
|
2019
|
+
'intLim',
|
|
2020
|
+
'naryLim',
|
|
2021
|
+
'mcJc',
|
|
2022
|
+
'degHide'
|
|
1976
2023
|
]);
|
|
1977
2024
|
class Pr {
|
|
1978
2025
|
get(name) {
|
|
@@ -1997,6 +2044,17 @@ function getAttr(node, attrLocal) {
|
|
|
1997
2044
|
return null == v ? void 0 : v;
|
|
1998
2045
|
}
|
|
1999
2046
|
}
|
|
2047
|
+
function findDescendantAttr(node, elemLocal, attrLocal) {
|
|
2048
|
+
for (const child of node.children){
|
|
2049
|
+
if (!isElement(child)) continue;
|
|
2050
|
+
if (localName(child.tagName) === elemLocal) {
|
|
2051
|
+
const v = getAttr(child, attrLocal);
|
|
2052
|
+
if (void 0 !== v) return v;
|
|
2053
|
+
}
|
|
2054
|
+
const deep = findDescendantAttr(child, elemLocal, attrLocal);
|
|
2055
|
+
if (void 0 !== deep) return deep;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2000
2058
|
const DIRECT_TAGS = new Set([
|
|
2001
2059
|
'box',
|
|
2002
2060
|
'sSub',
|
|
@@ -2086,6 +2144,12 @@ class Converter {
|
|
|
2086
2144
|
return this.doMr(elm);
|
|
2087
2145
|
case 'nary':
|
|
2088
2146
|
return this.doNary(elm);
|
|
2147
|
+
case 'borderBox':
|
|
2148
|
+
return this.doBorderBox(elm);
|
|
2149
|
+
case 'phant':
|
|
2150
|
+
return this.doPhant(elm);
|
|
2151
|
+
case 'sPre':
|
|
2152
|
+
return this.doSPre(elm);
|
|
2089
2153
|
default:
|
|
2090
2154
|
return null;
|
|
2091
2155
|
}
|
|
@@ -2120,7 +2184,12 @@ class Converter {
|
|
|
2120
2184
|
const nullChar = D_DEFAULT["null"];
|
|
2121
2185
|
const sVal = getVal(pr?.get('begChr'), D_DEFAULT.left, T);
|
|
2122
2186
|
const eVal = getVal(pr?.get('endChr'), D_DEFAULT.right, T);
|
|
2123
|
-
const
|
|
2187
|
+
const sepVal = getVal(pr?.get('sepChr'), D_DEFAULT.sep, T);
|
|
2188
|
+
const bases = this.processChildrenList(elm, new Set([
|
|
2189
|
+
'e'
|
|
2190
|
+
])).map((x)=>String(x.value));
|
|
2191
|
+
const sep = sepVal ? escapeLatex(sepVal) : '';
|
|
2192
|
+
const text = bases.join(sep);
|
|
2124
2193
|
return (pr?.text ?? '') + pyFormat(D, [], {
|
|
2125
2194
|
left: sVal ? escapeLatex(sVal) : nullChar,
|
|
2126
2195
|
text,
|
|
@@ -2140,7 +2209,8 @@ class Converter {
|
|
|
2140
2209
|
doF(elm) {
|
|
2141
2210
|
const c = this.processChildrenDict(elm);
|
|
2142
2211
|
const pr = c['fPr'];
|
|
2143
|
-
|
|
2212
|
+
let latexS = getVal(pr?.get('type'), F_DEFAULT, F) ?? '';
|
|
2213
|
+
if (latexS === F_DEFAULT && isOn(pr?.get('smallFrac'))) latexS = F_SMALL;
|
|
2144
2214
|
const num = c['num'] ?? '';
|
|
2145
2215
|
const den = c['den'] ?? '';
|
|
2146
2216
|
return (pr?.text ?? '') + pyFormat(latexS, [], {
|
|
@@ -2229,10 +2299,16 @@ class Converter {
|
|
|
2229
2299
|
}
|
|
2230
2300
|
doM(elm) {
|
|
2231
2301
|
const rows = [];
|
|
2232
|
-
|
|
2302
|
+
let template = M;
|
|
2303
|
+
for (const { stag, value, node } of this.processChildrenList(elm)){
|
|
2304
|
+
if ('mPr' === stag) {
|
|
2305
|
+
const jc = findDescendantAttr(node, 'mcJc', VAL_SUFFIX);
|
|
2306
|
+
if (jc && M_JC[jc]) template = M_JC[jc];
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2233
2309
|
if ('mr' === stag) rows.push(String(value));
|
|
2234
2310
|
}
|
|
2235
|
-
return pyFormat(
|
|
2311
|
+
return pyFormat(template, [], {
|
|
2236
2312
|
text: rows.join("\\\\")
|
|
2237
2313
|
});
|
|
2238
2314
|
}
|
|
@@ -2244,9 +2320,37 @@ class Converter {
|
|
|
2244
2320
|
doNary(elm) {
|
|
2245
2321
|
const res = [];
|
|
2246
2322
|
let bo = '';
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2323
|
+
let limLoc = '';
|
|
2324
|
+
for (const { stag, value } of this.processChildrenList(elm))if ('naryPr' === stag) {
|
|
2325
|
+
const pr = value;
|
|
2326
|
+
bo = getVal(pr.get('chr'), void 0, CHR_BO) ?? '';
|
|
2327
|
+
limLoc = pr.get('naryLim') ?? pr.get('limLoc') ?? pr.get('intLim') ?? '';
|
|
2328
|
+
} else res.push(String(value));
|
|
2329
|
+
const limits = 'undOvr' === limLoc ? '\\limits' : '';
|
|
2330
|
+
return bo + limits + res.join("");
|
|
2331
|
+
}
|
|
2332
|
+
doBorderBox(elm) {
|
|
2333
|
+
const c = this.processChildrenDict(elm);
|
|
2334
|
+
const e = c['e'] ?? '';
|
|
2335
|
+
return pyFormat(BORDER_BOX, [
|
|
2336
|
+
e
|
|
2337
|
+
]);
|
|
2338
|
+
}
|
|
2339
|
+
doPhant(elm) {
|
|
2340
|
+
const c = this.processChildrenDict(elm);
|
|
2341
|
+
const pr = c['phantPr'];
|
|
2342
|
+
const e = c['e'] ?? '';
|
|
2343
|
+
if (isOn(pr?.get('show'), true)) return e;
|
|
2344
|
+
return pyFormat(PHANT, [
|
|
2345
|
+
e
|
|
2346
|
+
]);
|
|
2347
|
+
}
|
|
2348
|
+
doSPre(elm) {
|
|
2349
|
+
const c = this.processChildrenDict(elm);
|
|
2350
|
+
const e = c['e'] ?? '';
|
|
2351
|
+
const sub = c['sub'] ?? '';
|
|
2352
|
+
const sup = c['sup'] ?? '';
|
|
2353
|
+
return `{}${sub}${sup}{${e}}`;
|
|
2250
2354
|
}
|
|
2251
2355
|
processUnicode(s) {
|
|
2252
2356
|
let out = '';
|