dwml-ts 0.4.0

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 ADDED
@@ -0,0 +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
@@ -0,0 +1,76 @@
1
+ import { TNode } from 'txml';
2
+
3
+ export declare function elementChildren(node: TNode): TNode[];
4
+
5
+ /**
6
+ * Escape LaTeX-special characters, matching dwml's `escape_latex`:
7
+ * `\\` is first collapsed to `\`, then any CHARS member not preceded by a
8
+ * backslash is prefixed with a backslash.
9
+ */
10
+ export declare function escapeLatex(strs: string): string;
11
+
12
+ /**
13
+ * Find and convert every `oMath` element in any OOXML document or fragment,
14
+ * returning one bare LaTeX string per formula (in document order).
15
+ */
16
+ export declare function extractOmmlLatex(xml: string, options?: OmmlToLatexOptions): string[];
17
+
18
+ /** Local name of a tag: strips any namespace prefix (`m:oMath` -> `oMath`). */
19
+ export declare function localName(tagName: string): string;
20
+
21
+ /**
22
+ * Convert an already-parsed `oMath` (or oMath-like) node to bare LaTeX.
23
+ * Never throws on unknown constructs.
24
+ */
25
+ export declare function ommlNodeToLatex(node: TNode, options?: OmmlToLatexOptions): string;
26
+
27
+ /**
28
+ * Convert one OMML fragment to bare LaTeX.
29
+ *
30
+ * Accepts `<m:oMath>`, `<m:oMathPara>` or the `<a14:m>` wrapper as an XML
31
+ * string. Tolerates missing xmlns declarations (fragments sliced out of
32
+ * slide XML). Returns bare LaTeX — no $/$$ delimiters, no trailing
33
+ * whitespace. If the fragment contains multiple oMath elements, their LaTeX
34
+ * is concatenated.
35
+ */
36
+ export declare function ommlToLatex(xml: string, options?: OmmlToLatexOptions): string;
37
+
38
+ /** Options accepted by the conversion entry points. */
39
+ export declare interface OmmlToLatexOptions {
40
+ /** Called once per element local-name the converter had to degrade. */
41
+ onUnsupported?: (localName: string) => void;
42
+ }
43
+
44
+ /**
45
+ * Concatenated text content of a node (the "never throw" degradation target).
46
+ */
47
+ export declare function textContent(node: TNode): string;
48
+
49
+ export { TNode }
50
+
51
+ /**
52
+ * Unicode -> LaTeX encoder.
53
+ *
54
+ * Internalized port of pylatexenc 2.x `latexencode.UnicodeToLatexEncoder`
55
+ * (MIT, (c) Philippe Faist), restricted to the configuration dwml uses:
56
+ * replacement_latex_protection = 'braces-all'
57
+ * unknown_char_policy = 'keep'
58
+ * unknown_char_warning = false
59
+ * non_ascii_only = false
60
+ * conversion_rules = ['defaults']
61
+ *
62
+ * The `braces-all` scheme wraps every rule replacement in curly braces.
63
+ * Unknown characters (no mapping) are kept verbatim.
64
+ */
65
+ /**
66
+ * Convert a unicode string to a LaTeX snippet using the default
67
+ * uni2latex map with `braces-all` protection, NFC normalization, and
68
+ * `keep` policy for unknown characters.
69
+ *
70
+ * Iteration is by Unicode code point (matching Python's str indexing),
71
+ * so astral-plane characters (e.g. Mathematical Alphanumeric Symbols
72
+ * U+1D400–U+1D7FF) are treated as single characters.
73
+ */
74
+ export declare function unicodeToLatex(input: string): string;
75
+
76
+ export { }