@fiduswriter/document 0.1.0-alpha.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.
Files changed (110) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +16 -0
  3. package/jest.config.js +23 -0
  4. package/package.json +59 -0
  5. package/schema.json +1 -0
  6. package/scripts/export-schema.js +16 -0
  7. package/src/bibliography/common.js +92 -0
  8. package/src/bibliography/csl_bib.js +139 -0
  9. package/src/citations/citeproc_sys.js +42 -0
  10. package/src/citations/format.js +194 -0
  11. package/src/common/blob.js +10 -0
  12. package/src/common/file.js +25 -0
  13. package/src/common/index.js +12 -0
  14. package/src/common/network.js +79 -0
  15. package/src/common/text.js +44 -0
  16. package/src/editor/e2ee/encryptor.js +228 -0
  17. package/src/exporter/docx/citations.js +177 -0
  18. package/src/exporter/docx/comments.js +165 -0
  19. package/src/exporter/docx/footnotes.js +240 -0
  20. package/src/exporter/docx/images.js +101 -0
  21. package/src/exporter/docx/index.js +185 -0
  22. package/src/exporter/docx/lists.js +260 -0
  23. package/src/exporter/docx/math.js +46 -0
  24. package/src/exporter/docx/metadata.js +289 -0
  25. package/src/exporter/docx/rels.js +193 -0
  26. package/src/exporter/docx/render.js +941 -0
  27. package/src/exporter/docx/richtext.js +1182 -0
  28. package/src/exporter/docx/tables.js +112 -0
  29. package/src/exporter/docx/tools.js +50 -0
  30. package/src/exporter/epub/index.js +142 -0
  31. package/src/exporter/epub/templates.js +140 -0
  32. package/src/exporter/epub/tools.js +96 -0
  33. package/src/exporter/html/citations.js +121 -0
  34. package/src/exporter/html/convert.js +813 -0
  35. package/src/exporter/html/index.js +192 -0
  36. package/src/exporter/html/templates.js +34 -0
  37. package/src/exporter/html/tools.js +50 -0
  38. package/src/exporter/jats/bibliography.js +183 -0
  39. package/src/exporter/jats/citations.js +109 -0
  40. package/src/exporter/jats/convert.js +871 -0
  41. package/src/exporter/jats/index.js +92 -0
  42. package/src/exporter/jats/templates.js +35 -0
  43. package/src/exporter/jats/text.js +72 -0
  44. package/src/exporter/latex/convert.js +934 -0
  45. package/src/exporter/latex/escape_latex.js +21 -0
  46. package/src/exporter/latex/index.js +74 -0
  47. package/src/exporter/latex/readme.js +22 -0
  48. package/src/exporter/native/shrink.js +132 -0
  49. package/src/exporter/odt/citations.js +101 -0
  50. package/src/exporter/odt/footnotes.js +147 -0
  51. package/src/exporter/odt/images.js +115 -0
  52. package/src/exporter/odt/index.js +156 -0
  53. package/src/exporter/odt/math.js +57 -0
  54. package/src/exporter/odt/metadata.js +251 -0
  55. package/src/exporter/odt/render.js +806 -0
  56. package/src/exporter/odt/richtext.js +865 -0
  57. package/src/exporter/odt/styles.js +387 -0
  58. package/src/exporter/odt/track.js +68 -0
  59. package/src/exporter/pandoc/citations.js +98 -0
  60. package/src/exporter/pandoc/convert.js +1017 -0
  61. package/src/exporter/pandoc/index.js +92 -0
  62. package/src/exporter/pandoc/readme.js +8 -0
  63. package/src/exporter/pandoc/tools.js +51 -0
  64. package/src/exporter/print/index.js +177 -0
  65. package/src/exporter/tools/doc_content.js +144 -0
  66. package/src/exporter/tools/file.js +9 -0
  67. package/src/exporter/tools/json.js +73 -0
  68. package/src/exporter/tools/svg.js +29 -0
  69. package/src/exporter/tools/xml.js +531 -0
  70. package/src/exporter/tools/xml_zip.js +95 -0
  71. package/src/exporter/tools/zip.js +90 -0
  72. package/src/exporter/tools/zotero_csl.js +93 -0
  73. package/src/importer/citations.js +129 -0
  74. package/src/importer/docx/citations.js +123 -0
  75. package/src/importer/docx/convert.js +1427 -0
  76. package/src/importer/docx/helpers.js +9 -0
  77. package/src/importer/docx/omml2mathml.js +1448 -0
  78. package/src/importer/docx/parse.js +735 -0
  79. package/src/importer/native/get_images.js +76 -0
  80. package/src/importer/native/update.js +29 -0
  81. package/src/importer/odt/citations.js +87 -0
  82. package/src/importer/odt/convert.js +1855 -0
  83. package/src/importer/pandoc/convert.js +884 -0
  84. package/src/importer/pandoc/helpers.js +84 -0
  85. package/src/importer/zip_analyzer.js +102 -0
  86. package/src/index.js +1 -0
  87. package/src/mathlive/opf_includes.js +24 -0
  88. package/src/schema/common/annotate.js +76 -0
  89. package/src/schema/common/base.js +118 -0
  90. package/src/schema/common/citation.js +62 -0
  91. package/src/schema/common/equation.js +31 -0
  92. package/src/schema/common/figure.js +190 -0
  93. package/src/schema/common/heading.js +43 -0
  94. package/src/schema/common/index.js +40 -0
  95. package/src/schema/common/list.js +95 -0
  96. package/src/schema/common/reference.js +100 -0
  97. package/src/schema/common/table.js +103 -0
  98. package/src/schema/common/track.js +190 -0
  99. package/src/schema/const.js +58 -0
  100. package/src/schema/convert.js +1272 -0
  101. package/src/schema/document/content.js +187 -0
  102. package/src/schema/document/index.js +117 -0
  103. package/src/schema/document/structure.js +452 -0
  104. package/src/schema/export.js +21 -0
  105. package/src/schema/footnotes.js +126 -0
  106. package/src/schema/footnotes_convert.js +31 -0
  107. package/src/schema/i18n.js +595 -0
  108. package/src/schema/index.js +5 -0
  109. package/src/schema/mini_json.js +61 -0
  110. package/src/schema/text.js +22 -0
@@ -0,0 +1,1448 @@
1
+ // Converted version of https://github.com/scienceai/omml2mathml/blob/master/index.js
2
+ // that works with our xml dom.
3
+
4
+ import {xmlDOM} from "../../exporter/tools/xml.js"
5
+ const MATH_NS = "http://www.w3.org/1998/Math/MathML"
6
+
7
+ // Regular expression matching mathematical operators
8
+ const oprx =
9
+ /[\+\-\*\/\^\&\|\!\~\<\>\=\:\u2208\u2209\u220B\u220C\u2218\u2219\u221D\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222A\u222B\u222C\u222D\u2234\u2235\u2236\u2237\u2238\u2239\u223A\u223B\u223C\u223D\u223E\u223F\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224A\u224B\u224C\u224D\u224E\u224F\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225A\u225B\u225C\u225D\u225E\u225F\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226A\u226B\u226C\u226D\u226E\u226F\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227A\u227B\u227C\u227D\u227E\u227F\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228A\u228B\u228C\u228D\u228E\u228F\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229A\u229B\u229C\u229D\u229E\u229F\u22A0\u22A1\u22A2\u22A3\u22A4\u22A5\u22A6\u22A7\u22A8\u22A9\u22AA\u22AB\u22AC\u22AD\u22AE\u22AF\u22B0\u22B1\u22B2\u22B3\u22B4\u22B5\u22B6\u22B7\u22B8\u22B9\u22BA\u22BB\u22BC\u22BD\u22C0\u22C1\u22C2\u22C3\u22C4\u22C5\u22C6\u22C7\u22C8\u22C9\u22CA\u22CB\u22CC\u22CD\u22CE\u22CF\u22D0\u22D1\u22D2\u22D3\u22D4\u22D5\u22D6\u22D7\u22D8\u22D9\u22DA\u22DB\u22DC\u22DD\u22DE\u22DF\u22E0\u22E1\u22E2\u22E3\u22E4\u22E5\u22E6\u22E7\u22E8\u22E9\u22EA\u22EB\u22EC\u22ED\u22EE\u22EF\u22F0\u22F1\u22F2\u22F3\u22F4\u22F5\u22F6\u22F7\u22F8\u22F9\u22FA\u22FB\u22FC\u22FD\u22FE\u22FF]/
10
+
11
+ /**
12
+ * Converts OMML to MathML
13
+ * @param {XMLElement} omml - OMML XML element
14
+ * @return {string} MathML XML string
15
+ */
16
+ export function omml2mathml(omml) {
17
+ // Create the root math element
18
+ const math = xmlDOM(`<math xmlns="${MATH_NS}" display="inline"></math>`)
19
+
20
+ // Process the OMML document
21
+ processOMML(omml, math)
22
+
23
+ return math
24
+ }
25
+
26
+ /**
27
+ * Process the OMML document and convert to MathML
28
+ * @param {XMLElement} omml - The OMML element to process
29
+ * @param {XMLElement} math - The parent MathML element
30
+ */
31
+ function processOMML(omml, math) {
32
+ // Handle different OMML elements
33
+ if (omml.tagName === "m:oMathPara") {
34
+ math.setAttribute("display", "block")
35
+ omml.queryAll("m:oMath").forEach(omath => {
36
+ processOMML(omath, math)
37
+ })
38
+ } else if (omml.tagName === "m:oMath") {
39
+ const mrow = createMathElement("mrow", {}, math)
40
+ processChildren(omml, mrow)
41
+ } else {
42
+ processElement(omml, math)
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Process an OMML element and create corresponding MathML
48
+ * @param {XMLElement} element - The OMML element to process
49
+ * @param {XMLElement} parent - The parent MathML element
50
+ */
51
+ function processElement(element, parent) {
52
+ if (!element || !element.tagName) {
53
+ return
54
+ }
55
+
56
+ switch (element.tagName) {
57
+ case "m:f":
58
+ processFraction(element, parent)
59
+ break
60
+ case "m:r":
61
+ processRun(element, parent)
62
+ break
63
+ case "m:limLow":
64
+ processLimLow(element, parent)
65
+ break
66
+ case "m:limUpp":
67
+ processLimUpp(element, parent)
68
+ break
69
+ case "m:sSub":
70
+ processSubscript(element, parent)
71
+ break
72
+ case "m:sSup":
73
+ processSuperscript(element, parent)
74
+ break
75
+ case "m:sSubSup":
76
+ processSubSuperscript(element, parent)
77
+ break
78
+ case "m:sPre":
79
+ processPreScript(element, parent)
80
+ break
81
+ case "m:m":
82
+ processMatrix(element, parent)
83
+ break
84
+ case "m:rad":
85
+ processRadical(element, parent)
86
+ break
87
+ case "m:nary":
88
+ processNary(element, parent)
89
+ break
90
+ case "m:d":
91
+ processDelimiter(element, parent)
92
+ break
93
+ case "m:eqArr":
94
+ processEqArr(element, parent)
95
+ break
96
+ case "m:func":
97
+ processFunction(element, parent)
98
+ break
99
+ case "m:acc":
100
+ processAccent(element, parent)
101
+ break
102
+ case "m:groupChr":
103
+ processGroupChar(element, parent)
104
+ break
105
+ case "m:borderBox":
106
+ processBorderBox(element, parent)
107
+ break
108
+ case "m:bar":
109
+ processBar(element, parent)
110
+ break
111
+ case "m:phant":
112
+ processPhantom(element, parent)
113
+ break
114
+ case "m:e":
115
+ case "m:den":
116
+ case "m:num":
117
+ case "m:lim":
118
+ case "m:sup":
119
+ case "m:sub":
120
+ processArgument(element, parent)
121
+ break
122
+ default:
123
+ // Process children for unhandled elements
124
+ processChildren(element, parent)
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Process all children of an element
130
+ * @param {XMLElement} element - The element whose children to process
131
+ * @param {XMLElement} parent - The parent MathML element
132
+ */
133
+ function processChildren(element, parent) {
134
+ if (!element || !element.children) {
135
+ return
136
+ }
137
+
138
+ element.children.forEach(child => {
139
+ if (typeof child === "object") {
140
+ processElement(child, parent)
141
+ }
142
+ })
143
+ }
144
+
145
+ /**
146
+ * Create a MathML element with specified attributes
147
+ * @param {string} tag - The MathML tag name
148
+ * @param {Object} attrs - The attributes to set
149
+ * @param {XMLElement} parent - The parent element
150
+ * @return {XMLElement} The created element
151
+ */
152
+ function createMathElement(tag, attrs = {}, parent = null) {
153
+ const elem = xmlDOM(`<${tag}></${tag}>`)
154
+
155
+ // Set attributes
156
+ Object.entries(attrs).forEach(([key, value]) => {
157
+ if (value !== undefined && value !== "") {
158
+ elem.setAttribute(key, value)
159
+ }
160
+ })
161
+
162
+ if (parent) {
163
+ parent.appendChild(elem)
164
+ }
165
+
166
+ return elem
167
+ }
168
+
169
+ /**
170
+ * Process a fraction element
171
+ * @param {XMLElement} element - The OMML fraction element
172
+ * @param {XMLElement} parent - The parent MathML element
173
+ */
174
+ function processFraction(element, parent) {
175
+ const type = getAttr(element, "m:fPr/m:type", "m:val") || ""
176
+
177
+ if (type.toLowerCase() === "lin") {
178
+ const mrow = createMathElement("mrow", {}, parent)
179
+ const numRow = createMathElement("mrow", {}, mrow)
180
+ const num = element.query("m:num")
181
+ if (num) {
182
+ processElement(num, numRow)
183
+ }
184
+ const mo = createMathElement("mo", {}, mrow)
185
+ mo.textContent = "/"
186
+
187
+ const denRow = createMathElement("mrow", {}, mrow)
188
+ const den = element.query("m:den")
189
+ if (den) {
190
+ processElement(den, denRow)
191
+ }
192
+ } else {
193
+ const attr = getFracProps(type.toLowerCase())
194
+ const mfrac = createMathElement("mfrac", attr, parent)
195
+
196
+ const numRow = createMathElement("mrow", {}, mfrac)
197
+ const num = element.query("m:num")
198
+ if (num) {
199
+ processElement(num, numRow)
200
+ }
201
+ const denRow = createMathElement("mrow", {}, mfrac)
202
+ const den = element.query("m:den")
203
+ if (den) {
204
+ processElement(den, denRow)
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Process a run of text
211
+ * @param {XMLElement} element - The OMML run element
212
+ * @param {XMLElement} parent - The parent MathML element
213
+ */
214
+ function processRun(element, parent) {
215
+ const nor = forceFalse(getAttr(element, "m:rPr/m:nor", "m:val") || "false")
216
+ if (nor) {
217
+ const mtext = createMathElement("mtext", {}, parent)
218
+ const textContent = element
219
+ .queryAll("m:t")
220
+ .map(t => t.textContent)
221
+ .join("")
222
+ mtext.textContent = nbsp(textContent)
223
+ } else {
224
+ element.queryAll("m:t").forEach(t => {
225
+ const toParse = t.textContent
226
+ const scr = getAttr(element, "m:rPr/m:scr", "m:val")
227
+ const sty = getAttr(element, "m:rPr/m:sty", "m:val")
228
+ parseMT(element, parent, {
229
+ toParse,
230
+ scr,
231
+ sty,
232
+ nor: false
233
+ })
234
+ })
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Process a lower limit element
240
+ * @param {XMLElement} element - The OMML limLow element
241
+ * @param {XMLElement} parent - The parent MathML element
242
+ */
243
+ function processLimLow(element, parent) {
244
+ const munder = createMathElement("munder", {}, parent)
245
+ const row1 = createMathElement("mrow", {}, munder)
246
+ const row2 = createMathElement("mrow", {}, munder)
247
+
248
+ const e = element.query("m:e")
249
+ if (e) {
250
+ processElement(e, row1)
251
+ }
252
+
253
+ const lim = element.query("m:lim")
254
+ if (lim) {
255
+ processElement(lim, row2)
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Process an upper limit element
261
+ * @param {XMLElement} element - The OMML limUpp element
262
+ * @param {XMLElement} parent - The parent MathML element
263
+ */
264
+ function processLimUpp(element, parent) {
265
+ const mover = createMathElement("mover", {}, parent)
266
+ const row1 = createMathElement("mrow", {}, mover)
267
+ const row2 = createMathElement("mrow", {}, mover)
268
+
269
+ const e = element.query("m:e")
270
+ if (e) {
271
+ processElement(e, row1)
272
+ }
273
+
274
+ const lim = element.query("m:lim")
275
+ if (lim) {
276
+ processElement(lim, row2)
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Process a subscript element
282
+ * @param {XMLElement} element - The OMML sSub element
283
+ * @param {XMLElement} parent - The parent MathML element
284
+ */
285
+ function processSubscript(element, parent) {
286
+ const msub = createMathElement("msub", {}, parent)
287
+ const row1 = createMathElement("mrow", {}, msub)
288
+ const row2 = createMathElement("mrow", {}, msub)
289
+
290
+ const e = element.query("m:e")
291
+ if (e) {
292
+ processElement(e, row1)
293
+ }
294
+
295
+ const sub = element.query("m:sub")
296
+ if (sub) {
297
+ processElement(sub, row2)
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Process a superscript element
303
+ * @param {XMLElement} element - The OMML sSup element
304
+ * @param {XMLElement} parent - The parent MathML element
305
+ */
306
+ function processSuperscript(element, parent) {
307
+ const msup = createMathElement("msup", {}, parent)
308
+ const row1 = createMathElement("mrow", {}, msup)
309
+ const row2 = createMathElement("mrow", {}, msup)
310
+
311
+ const e = element.query("m:e")
312
+ if (e) {
313
+ processElement(e, row1)
314
+ }
315
+
316
+ const sup = element.query("m:sup")
317
+ if (sup) {
318
+ processElement(sup, row2)
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Process a subscript-superscript element
324
+ * @param {XMLElement} element - The OMML sSubSup element
325
+ * @param {XMLElement} parent - The parent MathML element
326
+ */
327
+ function processSubSuperscript(element, parent) {
328
+ const msubsup = createMathElement("msubsup", {}, parent)
329
+ const row1 = createMathElement("mrow", {}, msubsup)
330
+ const row2 = createMathElement("mrow", {}, msubsup)
331
+ const row3 = createMathElement("mrow", {}, msubsup)
332
+
333
+ const e = element.query("m:e")
334
+ if (e) {
335
+ processElement(e, row1)
336
+ }
337
+
338
+ const sub = element.query("m:sub")
339
+ if (sub) {
340
+ processElement(sub, row2)
341
+ }
342
+
343
+ const sup = element.query("m:sup")
344
+ if (sup) {
345
+ processElement(sup, row3)
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Process a prescripted element
351
+ * @param {XMLElement} element - The OMML sPre element
352
+ * @param {XMLElement} parent - The parent MathML element
353
+ */
354
+ function processPreScript(element, parent) {
355
+ const mmultiscripts = createMathElement("mmultiscripts", {}, parent)
356
+ const row = createMathElement("mrow", {}, mmultiscripts)
357
+
358
+ const e = element.query("m:e")
359
+ if (e) {
360
+ processElement(e, row)
361
+ }
362
+
363
+ createMathElement("mprescripts", {}, mmultiscripts)
364
+
365
+ const sub = element.query("m:sub")
366
+ outputScript(mmultiscripts, sub)
367
+
368
+ const sup = element.query("m:sup")
369
+ outputScript(mmultiscripts, sup)
370
+ }
371
+
372
+ /**
373
+ * Process a matrix element
374
+ * @param {XMLElement} element - The OMML matrix element
375
+ * @param {XMLElement} parent - The parent MathML element
376
+ */
377
+ function processMatrix(element, parent) {
378
+ const mcjc = getAttr(element, "m:mPr/m:mcs/m:mc/m:mcPr/m:mcJc", "m:val")
379
+
380
+ const attrs = {}
381
+ if (mcjc && mcjc.toLowerCase() !== "center") {
382
+ attrs.columnalign = mcjc.toLowerCase()
383
+ }
384
+
385
+ const mtable = createMathElement("mtable", attrs, parent)
386
+
387
+ element.queryAll("m:mr").forEach(mr => {
388
+ const mtr = createMathElement("mtr", {}, mtable)
389
+
390
+ mr.queryAll("m:e").forEach(me => {
391
+ const mtd = createMathElement("mtd", {}, mtr)
392
+ processElement(me, mtd)
393
+ })
394
+ })
395
+ }
396
+
397
+ /**
398
+ * Process a radical element
399
+ * @param {XMLElement} element - The OMML radical element
400
+ * @param {XMLElement} parent - The parent MathML element
401
+ */
402
+ function processRadical(element, parent) {
403
+ const degHide = forceFalse(
404
+ getAttr(element, "m:radPr/m:degHide", "m:val") || "false"
405
+ )
406
+
407
+ if (degHide) {
408
+ const msqrt = createMathElement("msqrt", {}, parent)
409
+ const e = element.query("m:e")
410
+ if (e) {
411
+ processElement(e, msqrt)
412
+ }
413
+ } else {
414
+ const mroot = createMathElement("mroot", {}, parent)
415
+ const row1 = createMathElement("mrow", {}, mroot)
416
+ const row2 = createMathElement("mrow", {}, mroot)
417
+
418
+ const e = element.query("m:e")
419
+ if (e) {
420
+ processElement(e, row1)
421
+ }
422
+
423
+ const deg = element.query("m:deg")
424
+ if (deg) {
425
+ processElement(deg, row2)
426
+ }
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Process an n-ary operator element
432
+ * @param {XMLElement} element - The OMML nary element
433
+ * @param {XMLElement} parent - The parent MathML element
434
+ */
435
+ function processNary(element, parent) {
436
+ const subHide = forceFalse(
437
+ getAttr(element, "m:naryPr/m:subHide", "m:val") || "false"
438
+ )
439
+ const supHide = forceFalse(
440
+ getAttr(element, "m:naryPr/m:supHide", "m:val") || "false"
441
+ )
442
+ const limLocSubSup =
443
+ (getAttr(element, "m:naryPr/m:limLoc", "m:val") || "").toLowerCase() ===
444
+ "" ||
445
+ (getAttr(element, "m:naryPr/m:limLoc", "m:val") || "").toLowerCase() ===
446
+ "subsup"
447
+ const grow = forceFalse(
448
+ getAttr(element, "m:naryPr/m:grow", "m:val") || "false"
449
+ )
450
+
451
+ const mrow = createMathElement("mrow", {}, parent)
452
+
453
+ if (supHide && subHide) {
454
+ outputNAryMO(element, mrow, grow)
455
+ } else if (subHide) {
456
+ const outer = createMathElement(
457
+ limLocSubSup ? "msup" : "mover",
458
+ {},
459
+ mrow
460
+ )
461
+ outputNAryMO(element, outer, grow)
462
+
463
+ const suprow = createMathElement("mrow", {}, outer)
464
+ const sup = element.query("m:sup")
465
+ if (sup) {
466
+ processElement(sup, suprow)
467
+ }
468
+ } else if (supHide) {
469
+ const outer = createMathElement(
470
+ limLocSubSup ? "msub" : "munder",
471
+ {},
472
+ mrow
473
+ )
474
+ outputNAryMO(element, outer, grow)
475
+
476
+ const subrow = createMathElement("mrow", {}, outer)
477
+ const sub = element.query("m:sub")
478
+ if (sub) {
479
+ processElement(sub, subrow)
480
+ }
481
+ } else {
482
+ const outer = createMathElement(
483
+ limLocSubSup ? "msubsup" : "munderover",
484
+ {},
485
+ mrow
486
+ )
487
+ outputNAryMO(element, outer, grow)
488
+
489
+ const subrow1 = createMathElement("mrow", {}, outer)
490
+ const sub = element.query("m:sub")
491
+ if (sub) {
492
+ processElement(sub, subrow1)
493
+ }
494
+
495
+ const subrow2 = createMathElement("mrow", {}, outer)
496
+ const sup = element.query("m:sup")
497
+ if (sup) {
498
+ processElement(sup, subrow2)
499
+ }
500
+ }
501
+
502
+ const erow = createMathElement("mrow", {}, mrow)
503
+ const e = element.query("m:e")
504
+ if (e) {
505
+ processElement(e, erow)
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Process a delimiter element
511
+ * @param {XMLElement} element - The OMML delimiter element
512
+ * @param {XMLElement} parent - The parent MathML element
513
+ */
514
+ function processDelimiter(element, parent) {
515
+ const begChr = getAttr(element, "m:dPr/m:begChr", "m:val")
516
+ const endChr = getAttr(element, "m:dPr/m:endChr", "m:val")
517
+ const sepChr = getAttr(element, "m:dPr/m:sepChr", "m:val") || "|"
518
+
519
+ const attr = {}
520
+ if (begChr !== undefined && begChr !== "(") {
521
+ attr.open = begChr
522
+ }
523
+ if (endChr !== undefined && endChr !== ")") {
524
+ attr.close = endChr
525
+ }
526
+ if (sepChr !== ",") {
527
+ attr.separators = sepChr
528
+ }
529
+
530
+ const mfenced = createMathElement("mfenced", attr, parent)
531
+
532
+ element.queryAll("m:e").forEach(me => {
533
+ const row = createMathElement("mrow", {}, mfenced)
534
+ processElement(me, row)
535
+ })
536
+ }
537
+
538
+ /**
539
+ * Process an equation array element
540
+ * @param {XMLElement} element - The OMML eqArr element
541
+ * @param {XMLElement} parent - The parent MathML element
542
+ */
543
+ function processEqArr(element, parent) {
544
+ const mtable = createMathElement("mtable", {}, parent)
545
+
546
+ element.queryAll("m:e").forEach(me => {
547
+ const mtr = createMathElement("mtr", {}, mtable)
548
+ const mtd = createMathElement("mtd", {}, mtr)
549
+
550
+ const scrLvl = getAttr(me, "m:argPr/m:scrLvl", "m:val")
551
+ const outer =
552
+ scrLvl !== "0" && scrLvl
553
+ ? createMathElement("mrow", {}, mtd)
554
+ : createMathElement("mstyle", {scriptlevel: scrLvl}, mtd)
555
+
556
+ createMathElement("maligngroup", {}, outer)
557
+
558
+ const firstChild = me.children[0]
559
+ if (firstChild) {
560
+ createEqArrRow(outer, element, firstChild, 1)
561
+ }
562
+ })
563
+ }
564
+
565
+ /**
566
+ * Process a function element
567
+ * @param {XMLElement} element - The OMML function element
568
+ * @param {XMLElement} parent - The parent MathML element
569
+ */
570
+ function processFunction(element, parent) {
571
+ const outer = createMathElement("mrow", {}, parent)
572
+ const row1 = createMathElement("mrow", {}, outer)
573
+
574
+ element.queryAll("m:fName").forEach(fn => {
575
+ processElement(fn, row1)
576
+ })
577
+
578
+ const mo = createMathElement("mo", {}, outer)
579
+ mo.textContent = "\u2061" // Function application
580
+
581
+ const row2 = createMathElement("mrow", {}, outer)
582
+ const e = element.query("m:e")
583
+ if (e) {
584
+ processElement(e, row2)
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Process an accent element
590
+ * @param {XMLElement} element - The OMML accent element
591
+ * @param {XMLElement} parent - The parent MathML element
592
+ */
593
+ function processAccent(element, parent) {
594
+ const mover = createMathElement("mover", {accent: "true"}, parent)
595
+ const row = createMathElement("mrow", {}, mover)
596
+
597
+ const e = element.query("m:e")
598
+ if (e) {
599
+ processElement(e, row)
600
+ }
601
+
602
+ const acc = (getAttr(element, "m:accPr/m:chr", "m:val") || "\u0302").substr(
603
+ 0,
604
+ 1
605
+ )
606
+ const nonComb = toNonCombining(acc)
607
+
608
+ if (acc.length === 0) {
609
+ createMathElement("mo", {}, mover)
610
+ } else {
611
+ const nor = forceFalse(
612
+ getAttr(element, "m:rPr/m:nor", "m:val") || "false"
613
+ )
614
+ parseMT(element, mover, {
615
+ toParse: nonComb,
616
+ scr: getAttr(element, "m:e/*/m:rPr/m:scr", "m:val"),
617
+ sty: getAttr(element, "m:e/*/m:rPr/m:sty", "m:val"),
618
+ nor
619
+ })
620
+ }
621
+ }
622
+
623
+ /**
624
+ * Process a group character element
625
+ * @param {XMLElement} element - The OMML groupChr element
626
+ * @param {XMLElement} parent - The parent MathML element
627
+ */
628
+ function processGroupChar(element, parent) {
629
+ const lastGroupChrPr = element.query("m:groupChrPr")
630
+ if (!lastGroupChrPr) {
631
+ return
632
+ }
633
+
634
+ const pos = (getAttr(lastGroupChrPr, "m:pos", "m:val") || "").toLowerCase()
635
+ const vertJc = (
636
+ getAttr(lastGroupChrPr, "m:vertJc", "m:val") || ""
637
+ ).toLowerCase()
638
+ const chr = getAttr(lastGroupChrPr, "m:chr", "m:val") || "\u23DF"
639
+
640
+ const mkMrow = parent => {
641
+ const mrow = createMathElement("mrow", {}, parent)
642
+ const e = element.query("m:e")
643
+ if (e) {
644
+ processElement(e, mrow)
645
+ }
646
+ return mrow
647
+ }
648
+
649
+ const mkMo = parent => {
650
+ const mo = createMathElement("mo", {}, parent)
651
+ mo.textContent = chr.substr(0, 1)
652
+ return mo
653
+ }
654
+
655
+ if (pos === "top") {
656
+ if (vertJc === "bot") {
657
+ const outer = createMathElement("mover", {accent: "false"}, parent)
658
+ mkMrow(outer)
659
+ mkMo(outer)
660
+ } else {
661
+ const outer = createMathElement(
662
+ "munder",
663
+ {accentunder: "false"},
664
+ parent
665
+ )
666
+ mkMo(outer)
667
+ mkMrow(outer)
668
+ }
669
+ } else {
670
+ if (vertJc === "bot") {
671
+ const outer = createMathElement("mover", {accent: "false"}, parent)
672
+ mkMo(outer)
673
+ mkMrow(outer)
674
+ } else {
675
+ const outer = createMathElement(
676
+ "munder",
677
+ {accentunder: "false"},
678
+ parent
679
+ )
680
+ mkMrow(outer)
681
+ mkMo(outer)
682
+ }
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Process a border box element
688
+ * @param {XMLElement} element - The OMML borderBox element
689
+ * @param {XMLElement} parent - The parent MathML element
690
+ */
691
+ function processBorderBox(element, parent) {
692
+ const hideTop = forceTrue(
693
+ getAttr(element, "m:borderBoxPr/m:hideTop", "m:val") || "false"
694
+ )
695
+ const hideBot = forceTrue(
696
+ getAttr(element, "m:borderBoxPr/m:hideBot", "m:val") || "false"
697
+ )
698
+ const hideLeft = forceTrue(
699
+ getAttr(element, "m:borderBoxPr/m:hideLeft", "m:val") || "false"
700
+ )
701
+ const hideRight = forceTrue(
702
+ getAttr(element, "m:borderBoxPr/m:hideRight", "m:val") || "false"
703
+ )
704
+ const strikeH = forceTrue(
705
+ getAttr(element, "m:borderBoxPr/m:strikeH", "m:val") || "false"
706
+ )
707
+ const strikeV = forceTrue(
708
+ getAttr(element, "m:borderBoxPr/m:strikeV", "m:val") || "false"
709
+ )
710
+ const strikeBLTR = forceTrue(
711
+ getAttr(element, "m:borderBoxPr/m:strikeBLTR", "m:val") || "false"
712
+ )
713
+ const strikeTLBR = forceTrue(
714
+ getAttr(element, "m:borderBoxPr/m:strikeTLBR", "m:val") || "false"
715
+ )
716
+
717
+ let outer
718
+
719
+ if (
720
+ hideTop &&
721
+ hideBot &&
722
+ hideLeft &&
723
+ hideRight &&
724
+ !strikeH &&
725
+ !strikeV &&
726
+ !strikeBLTR &&
727
+ !strikeTLBR
728
+ ) {
729
+ outer = createMathElement("mrow", {}, parent)
730
+ } else {
731
+ const notation = createMEnclodeNotation({
732
+ hideTop,
733
+ hideBot,
734
+ hideLeft,
735
+ hideRight,
736
+ strikeH,
737
+ strikeV,
738
+ strikeBLTR,
739
+ strikeTLBR
740
+ })
741
+ outer = createMathElement("menclose", notation, parent)
742
+ }
743
+
744
+ const e = element.query("m:e")
745
+ if (e) {
746
+ processElement(e, outer)
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Process a bar element
752
+ * @param {XMLElement} element - The OMML bar element
753
+ * @param {XMLElement} parent - The parent MathML element
754
+ */
755
+ function processBar(element, parent) {
756
+ const pos = (getAttr(element, "m:barPr/m:pos", "m:val") || "").toLowerCase()
757
+
758
+ if (pos === "top") {
759
+ const outer = createMathElement("mover", {accent: "false"}, parent)
760
+ const row = createMathElement("mrow", {}, outer)
761
+ const mo = createMathElement("mo", {}, outer)
762
+
763
+ const e = element.query("m:e")
764
+ if (e) {
765
+ processElement(e, row)
766
+ }
767
+
768
+ mo.textContent = "\u00af" // Macron
769
+ } else {
770
+ const outer = createMathElement(
771
+ "munder",
772
+ {underaccent: "false"},
773
+ parent
774
+ )
775
+ const row = createMathElement("mrow", {}, outer)
776
+ const mo = createMathElement("mo", {}, outer)
777
+
778
+ const e = element.query("m:e")
779
+ if (e) {
780
+ processElement(e, row)
781
+ }
782
+
783
+ mo.textContent = "\u005f" // Underscore
784
+ }
785
+ }
786
+
787
+ /**
788
+ * Process a phantom element
789
+ * @param {XMLElement} element - The OMML phantom element
790
+ * @param {XMLElement} parent - The parent MathML element
791
+ */
792
+ function processPhantom(element, parent) {
793
+ const zeroWid = forceFalse(
794
+ getAttr(element, "m:phantPr/m:zeroWid", "m:val") || "false"
795
+ )
796
+ const zeroAsc = forceFalse(
797
+ getAttr(element, "m:phantPr/m:zeroAsc", "m:val") || "false"
798
+ )
799
+ const zeroDesc = forceFalse(
800
+ getAttr(element, "m:phantPr/m:zeroDesc", "m:val") || "false"
801
+ )
802
+ const showVal = forceFalse(
803
+ getAttr(element, "m:phantPr/m:show", "m:val") || "false"
804
+ )
805
+
806
+ let container
807
+
808
+ if (showVal) {
809
+ container = createMathElement(
810
+ "mpadded",
811
+ createMPaddedAttr({zeroWid, zeroAsc, zeroDesc}),
812
+ parent
813
+ )
814
+ } else if (!zeroWid && !zeroAsc && !zeroDesc) {
815
+ container = createMathElement("mphantom", {}, parent)
816
+ } else {
817
+ const phant = createMathElement("mphantom", {}, parent)
818
+ container = createMathElement(
819
+ "mpadded",
820
+ createMPaddedAttr({zeroWid, zeroAsc, zeroDesc}),
821
+ phant
822
+ )
823
+ }
824
+
825
+ const row = createMathElement("mrow", {}, container)
826
+ const e = element.query("m:e")
827
+ if (e) {
828
+ processElement(e, row)
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Process an argument element
834
+ * @param {XMLElement} element - The OMML argument element
835
+ * @param {XMLElement} parent - The parent MathML element
836
+ */
837
+ function processArgument(element, parent) {
838
+ const scriptlevel = getAttr(element, "m:argPr/m:scrLvl", "m:val")
839
+
840
+ if (!scriptlevel) {
841
+ processChildren(element, parent)
842
+ } else {
843
+ const style = createMathElement("mstyle", {scriptlevel}, parent)
844
+ processChildren(element, style)
845
+ }
846
+ }
847
+
848
+ /**
849
+ * Get attribute value from an element using a simplified XPath-like path
850
+ * @param {XMLElement} element - The element to query
851
+ * @param {string} path - The simplified path to the attribute
852
+ * @param {string} attrName - The attribute name
853
+ * @return {string} The attribute value or empty string
854
+ */
855
+ function getAttr(element, path, attrName) {
856
+ if (!element) {
857
+ return ""
858
+ }
859
+
860
+ const parts = path.split("/")
861
+ let current = element
862
+
863
+ for (let i = 0; i < parts.length; i++) {
864
+ if (!current) {
865
+ return ""
866
+ }
867
+
868
+ const part = parts[i]
869
+ if (part.includes("[last()]")) {
870
+ const tagName = part.replace("[last()]", "")
871
+ const elements = current.queryAll(tagName)
872
+ current = elements.length ? elements[elements.length - 1] : null
873
+ } else if (part.includes("[")) {
874
+ const match = part.match(/([^[]+)\[(\d+)\]/)
875
+ if (match) {
876
+ const tagName = match[1]
877
+ const index = parseInt(match[2], 10) - 1
878
+ const elements = current.queryAll(tagName)
879
+ current = elements[index] || null
880
+ } else {
881
+ current = current.query(part) || null
882
+ }
883
+ } else {
884
+ current = current.query(part) || null
885
+ }
886
+ }
887
+
888
+ return current ? current.getAttribute(attrName) || "" : ""
889
+ }
890
+
891
+ /**
892
+ * Output a script element, or "none" if not provided
893
+ * @param {XMLElement} parent - The parent element
894
+ * @param {XMLElement} element - The script element to output
895
+ */
896
+ function outputScript(parent, element) {
897
+ if (element) {
898
+ const row = createMathElement("mrow", {}, parent)
899
+ processElement(element, row)
900
+ } else {
901
+ createMathElement("none", {}, parent)
902
+ }
903
+ }
904
+
905
+ /**
906
+ * Output an n-ary operator
907
+ * @param {XMLElement} element - The OMML nary element
908
+ * @param {XMLElement} parent - The parent MathML element
909
+ * @param {boolean} grow - Whether the operator should stretch
910
+ */
911
+ function outputNAryMO(element, parent, grow = false) {
912
+ const mo = createMathElement(
913
+ "mo",
914
+ {stretchy: grow ? "true" : "false"},
915
+ parent
916
+ )
917
+ const val = getAttr(element, "m:naryPr/m:chr", "m:val")
918
+ mo.textContent = val || "\u222b" // Integral symbol by default
919
+ }
920
+
921
+ /**
922
+ * Create an equation array row
923
+ * @param {XMLElement} parent - The parent MathML element
924
+ * @param {XMLElement} src - The source OMML element
925
+ * @param {XMLElement} cur - The current OMML element
926
+ * @param {number} align - Alignment indicator
927
+ */
928
+ function createEqArrRow(parent, src, cur, align) {
929
+ if (!cur) {
930
+ return
931
+ }
932
+
933
+ if (cur.tagName === "m:r") {
934
+ const allMt = cur
935
+ .queryAll("m:t")
936
+ .map(t => t.textContent)
937
+ .join("")
938
+ const nor = forceFalse(getAttr(cur, "m:rPr/m:nor", "m:val") || "false")
939
+
940
+ parseEqArrMr(parent, {
941
+ toParse: allMt,
942
+ scr: getAttr(cur, "m:rPr/m:scr", "m:val"),
943
+ sty: getAttr(cur, "m:rPr/m:sty", "m:val"),
944
+ nor,
945
+ align
946
+ })
947
+ } else {
948
+ processElement(cur, parent)
949
+ }
950
+
951
+ // Get the next sibling if available
952
+ const siblings = cur.parentElement ? cur.parentElement.children : []
953
+ const index = siblings.indexOf(cur)
954
+ const nextSibling = index < siblings.length - 1 ? siblings[index + 1] : null
955
+
956
+ if (nextSibling) {
957
+ const allMt = cur
958
+ .queryAll("m:t")
959
+ .map(t => t.textContent)
960
+ .join("")
961
+ const amp = countAmp(allMt)
962
+ createEqArrRow(parent, src, nextSibling, (align + (amp % 2)) % 2)
963
+ }
964
+ }
965
+
966
+ /**
967
+ * Parse equation array run text
968
+ * @param {XMLElement} parent - The parent MathML element
969
+ * @param {Object} options - Parsing options
970
+ */
971
+ function parseEqArrMr(parent, {toParse = "", scr, sty, nor, align}) {
972
+ if (!toParse.length) {
973
+ return
974
+ }
975
+
976
+ if (toParse[0] === "&") {
977
+ createMathElement(align ? "malignmark" : "maligngroup", {}, parent)
978
+ parseEqArrMr(parent, {
979
+ toParse: toParse.substr(1),
980
+ align: !align,
981
+ scr,
982
+ sty,
983
+ nor
984
+ })
985
+ } else {
986
+ const firstOper = rxIndexOf(toParse, oprx)
987
+ const firstNum = rxIndexOf(toParse, /\d/)
988
+ const startsWithOper = firstOper === 1
989
+ const startsWithNum = firstNum === 1
990
+
991
+ if (!startsWithOper && !startsWithNum) {
992
+ if (!nor) {
993
+ const mi = createMathElement(
994
+ "mi",
995
+ tokenAttributes({
996
+ scr,
997
+ sty,
998
+ nor,
999
+ charToPrint: 1,
1000
+ tokenType: "mi"
1001
+ }),
1002
+ parent
1003
+ )
1004
+ mi.textContent = nbsp(toParse.substr(0, 1))
1005
+ } else {
1006
+ const mt = createMathElement("mtext", {}, parent)
1007
+ mt.textContent = nbsp(toParse.substr(0, 1))
1008
+ }
1009
+ parseEqArrMr(parent, {
1010
+ toParse: toParse.substr(1),
1011
+ scr,
1012
+ sty,
1013
+ nor,
1014
+ align
1015
+ })
1016
+ } else if (startsWithOper) {
1017
+ if (!nor) {
1018
+ const mo = createMathElement(
1019
+ "mo",
1020
+ tokenAttributes({
1021
+ nor,
1022
+ charToPrint: 1,
1023
+ tokenType: "mo"
1024
+ }),
1025
+ parent
1026
+ )
1027
+ mo.textContent = toParse.substr(0, 1)
1028
+ } else {
1029
+ const mt = createMathElement("mtext", {}, parent)
1030
+ mt.textContent = toParse.substr(0, 1)
1031
+ }
1032
+ parseEqArrMr(parent, {
1033
+ toParse: toParse.substr(1),
1034
+ scr,
1035
+ sty,
1036
+ nor,
1037
+ align
1038
+ })
1039
+ } else {
1040
+ const num = numStart(toParse)
1041
+ if (!nor) {
1042
+ const mn = createMathElement(
1043
+ "mn",
1044
+ tokenAttributes({
1045
+ sty: "p",
1046
+ nor,
1047
+ charToPrint: 1,
1048
+ tokenType: "mn"
1049
+ }),
1050
+ parent
1051
+ )
1052
+ mn.textContent = num
1053
+ } else {
1054
+ const mt = createMathElement("mtext", {}, parent)
1055
+ mt.textContent = num
1056
+ }
1057
+ parseEqArrMr(parent, {
1058
+ toParse: toParse.substr(num.length),
1059
+ scr,
1060
+ sty,
1061
+ nor,
1062
+ align
1063
+ })
1064
+ }
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * Parse math text
1070
+ * @param {XMLElement} ctx - The context OMML element
1071
+ * @param {XMLElement} parent - The parent MathML element
1072
+ * @param {Object} options - Parsing options
1073
+ */
1074
+ function parseMT(ctx, parent, {toParse = "", scr, sty, nor}) {
1075
+ if (!toParse.length) {
1076
+ return
1077
+ }
1078
+ const firstOper = rxIndexOf(toParse, oprx)
1079
+ const firstNum = rxIndexOf(toParse, /\d/)
1080
+ const startsWithOper = firstOper === 1
1081
+ const startsWithNum = firstNum === 1
1082
+ if (!startsWithOper && !startsWithNum) {
1083
+ let charToPrint = 1
1084
+ // Check if we're in a function name
1085
+ const inFuncName = ctx.closest("m:fName") !== null
1086
+ if (inFuncName) {
1087
+ if (!firstOper && !firstNum) {
1088
+ charToPrint = toParse.length
1089
+ } else {
1090
+ charToPrint =
1091
+ Math.min(
1092
+ firstOper || Number.MAX_VALUE,
1093
+ firstNum || Number.MAX_VALUE
1094
+ ) - 1
1095
+ }
1096
+ }
1097
+ const mi = createMathElement(
1098
+ "mi",
1099
+ tokenAttributes({
1100
+ scr,
1101
+ sty,
1102
+ nor,
1103
+ charToPrint,
1104
+ tokenType: "mi"
1105
+ }),
1106
+ parent
1107
+ )
1108
+ mi.textContent = nbsp(toParse.substr(0, charToPrint))
1109
+ parseMT(ctx, parent, {
1110
+ toParse: toParse.substr(charToPrint),
1111
+ scr,
1112
+ sty,
1113
+ nor
1114
+ })
1115
+ } else if (startsWithOper) {
1116
+ const mo = createMathElement(
1117
+ "mo",
1118
+ tokenAttributes({
1119
+ nor,
1120
+ tokenType: "mo"
1121
+ }),
1122
+ parent
1123
+ )
1124
+ mo.textContent = toParse.substr(0, 1)
1125
+
1126
+ parseMT(ctx, parent, {
1127
+ toParse: toParse.substr(1),
1128
+ scr,
1129
+ sty,
1130
+ nor
1131
+ })
1132
+ } else {
1133
+ const num = numStart(toParse)
1134
+ const mn = createMathElement(
1135
+ "mn",
1136
+ tokenAttributes({
1137
+ scr,
1138
+ sty: "p",
1139
+ nor,
1140
+ tokenType: "mn"
1141
+ }),
1142
+ parent
1143
+ )
1144
+ mn.textContent = num
1145
+
1146
+ parseMT(ctx, parent, {
1147
+ toParse: toParse.substr(num.length),
1148
+ scr,
1149
+ sty,
1150
+ nor
1151
+ })
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * Find the index of a regex match in a string
1157
+ * @param {string} str - The string to search
1158
+ * @param {RegExp} rx - The regex to match
1159
+ * @return {number} The match index + 1, or 0 if no match
1160
+ */
1161
+ function rxIndexOf(str, rx) {
1162
+ const re = rx.exec(str)
1163
+ if (!re) {
1164
+ return 0
1165
+ }
1166
+ return re.index + 1
1167
+ }
1168
+
1169
+ /**
1170
+ * Get the start of a number in a string
1171
+ * @param {string} str - The string to check
1172
+ * @return {string} The number at the start of the string
1173
+ */
1174
+ function numStart(str) {
1175
+ if (!str) {
1176
+ return ""
1177
+ }
1178
+ const match = str.match(/^(\d+)/)
1179
+ return match ? match[1] : ""
1180
+ }
1181
+
1182
+ /**
1183
+ * Count ampersands in a string
1184
+ * @param {string} str - The string to check
1185
+ * @return {number} The number of ampersands
1186
+ */
1187
+ function countAmp(str) {
1188
+ return ((str || "").match(/&/g) || []).length
1189
+ }
1190
+
1191
+ /**
1192
+ * Convert a combining character to its non-combining equivalent
1193
+ * @param {string} ch - The character to convert
1194
+ * @return {string} The non-combining equivalent
1195
+ */
1196
+ function toNonCombining(ch) {
1197
+ const combiMap = {
1198
+ "\u0306": "\u02D8", // breve
1199
+ "\u032e": "\u02D8", // breve below
1200
+ "\u0312": "\u00B8", // cedilla
1201
+ "\u0327": "\u00B8", // cedilla
1202
+ "\u0300": "\u0060", // grave
1203
+ "\u0316": "\u0060", // grave below
1204
+ "\u0305": "\u002D", // macron/overbar
1205
+ "\u0332": "\u002D", // macron/underbar
1206
+ "\u0323": "\u002E", // dot below
1207
+ "\u0307": "\u02D9", // dot above
1208
+ "\u030B": "\u02DD", // double acute
1209
+ "\u0317": "\u00B4", // acute below
1210
+ "\u0301": "\u00B4", // acute
1211
+ "\u0330": "\u007E", // tilde below
1212
+ "\u0303": "\u007E", // tilde
1213
+ "\u0324": "\u00A8", // diaeresis below
1214
+ "\u0308": "\u00A8", // diaeresis
1215
+ "\u032C": "\u02C7", // caron below
1216
+ "\u030C": "\u02C7", // caron
1217
+ "\u0302": "\u005E", // circumflex
1218
+ "\u032D": "\u005E", // circumflex below
1219
+ "\u20D7": "\u2192", // vector/right arrow
1220
+ "\u20EF": "\u2192", // vector/right arrow below
1221
+ "\u20D6": "\u2190", // left arrow
1222
+ "\u20EE": "\u2190" // left arrow below
1223
+ }
1224
+ return combiMap[ch] || ch
1225
+ }
1226
+
1227
+ /**
1228
+ * Create MathML token attributes based on token settings
1229
+ * @param {Object} options - Token options
1230
+ * @return {Object} Attribute object
1231
+ */
1232
+ function tokenAttributes({scr, sty, nor, charToPrint = 0, tokenType}) {
1233
+ const attr = {}
1234
+
1235
+ if (nor) {
1236
+ attr.mathvariant = "normal"
1237
+ } else {
1238
+ let mathvariant
1239
+ const fontweight = sty === "b" || sty === "bi" ? "bold" : "normal"
1240
+ const fontstyle = sty === "b" || sty === "p" ? "normal" : "italic"
1241
+
1242
+ if (tokenType !== "mn") {
1243
+ if (scr === "monospace") {
1244
+ mathvariant = "monospace"
1245
+ } else if (scr === "sans-serif" && sty === "i") {
1246
+ mathvariant = "sans-serif-italic"
1247
+ } else if (scr === "sans-serif" && sty === "b") {
1248
+ mathvariant = "bold-sans-serif"
1249
+ } else if (scr === "sans-serif" && sty === "bi") {
1250
+ mathvariant = "sans-serif-bold-italic"
1251
+ } else if (scr === "sans-serif") {
1252
+ mathvariant = "sans-serif"
1253
+ } else if (scr === "fraktur" && (sty === "b" || sty === "i")) {
1254
+ mathvariant = "bold-fraktur"
1255
+ } else if (scr === "fraktur") {
1256
+ mathvariant = "fraktur"
1257
+ } else if (scr === "double-struck") {
1258
+ mathvariant = "double-struck"
1259
+ } else if (scr === "script" && (sty === "b" || sty === "i")) {
1260
+ mathvariant = "bold-script"
1261
+ } else if (scr === "script") {
1262
+ mathvariant = "script"
1263
+ } else if (scr === "roman" || !scr) {
1264
+ if (sty === "b") {
1265
+ mathvariant = "bold"
1266
+ } else if (sty === "i") {
1267
+ mathvariant = "italic"
1268
+ } else if (sty === "p") {
1269
+ mathvariant = "normal"
1270
+ } else if (sty === "bi") {
1271
+ mathvariant = "bold-italic"
1272
+ }
1273
+ }
1274
+ }
1275
+
1276
+ if (tokenType === "mo" && mathvariant !== "normal") {
1277
+ return attr
1278
+ }
1279
+
1280
+ if (
1281
+ tokenType === "mi" &&
1282
+ charToPrint === 1 &&
1283
+ (mathvariant === "italic" || !mathvariant)
1284
+ ) {
1285
+ return attr
1286
+ }
1287
+
1288
+ if (
1289
+ tokenType === "mi" &&
1290
+ charToPrint > 1 &&
1291
+ (mathvariant === "italic" || !mathvariant)
1292
+ ) {
1293
+ attr.mathvariant = "italic"
1294
+ } else if (mathvariant && mathvariant !== "italic") {
1295
+ attr.mathvariant = mathvariant
1296
+ } else {
1297
+ if (
1298
+ fontstyle === "italic" &&
1299
+ !(tokenType === "mi" && charToPrint === 1)
1300
+ ) {
1301
+ attr.fontstyle = "italic"
1302
+ }
1303
+ if (fontweight === "bold") {
1304
+ attr.fontweight = "bold"
1305
+ }
1306
+ }
1307
+ }
1308
+
1309
+ return attr
1310
+ }
1311
+
1312
+ /**
1313
+ * Create menclose notation attribute value
1314
+ * @param {Object} options - Notation options
1315
+ * @return {Object} The notation attributes
1316
+ */
1317
+ function createMEnclodeNotation({
1318
+ hideTop,
1319
+ hideBot,
1320
+ hideLeft,
1321
+ hideRight,
1322
+ strikeH,
1323
+ strikeV,
1324
+ strikeBLTR,
1325
+ strikeTLBR
1326
+ }) {
1327
+ const notation = []
1328
+
1329
+ if (!hideTop && !hideBot && !hideLeft && !hideRight) {
1330
+ notation.push("box")
1331
+ } else {
1332
+ if (!hideTop) {
1333
+ notation.push("top")
1334
+ }
1335
+ if (!hideBot) {
1336
+ notation.push("bottom")
1337
+ }
1338
+ if (!hideLeft) {
1339
+ notation.push("left")
1340
+ }
1341
+ if (!hideRight) {
1342
+ notation.push("right")
1343
+ }
1344
+ }
1345
+
1346
+ if (strikeH) {
1347
+ notation.push("horizontalstrike")
1348
+ }
1349
+ if (strikeV) {
1350
+ notation.push("verticalstrike")
1351
+ }
1352
+ if (strikeBLTR) {
1353
+ notation.push("updiagonalstrike")
1354
+ }
1355
+ if (strikeTLBR) {
1356
+ notation.push("downdiagonalstrike")
1357
+ }
1358
+
1359
+ return {notation: notation.join(" ")}
1360
+ }
1361
+
1362
+ /**
1363
+ * Create mpadded attributes
1364
+ * @param {Object} options - Padding options
1365
+ * @return {Object} The padding attributes
1366
+ */
1367
+ function createMPaddedAttr({zeroWid, zeroAsc, zeroDesc}) {
1368
+ const attr = {}
1369
+
1370
+ if (zeroWid) {
1371
+ attr.width = "0in"
1372
+ }
1373
+ if (zeroAsc) {
1374
+ attr.height = "0in"
1375
+ }
1376
+ if (zeroDesc) {
1377
+ attr.depth = "0in"
1378
+ }
1379
+
1380
+ return attr
1381
+ }
1382
+
1383
+ /**
1384
+ * Get fraction properties
1385
+ * @param {string} type - Fraction type
1386
+ * @return {Object} Fraction attributes
1387
+ */
1388
+ function getFracProps(type) {
1389
+ if (type === "skw" || type === "lin") {
1390
+ return {bevelled: "true"}
1391
+ }
1392
+ if (type === "nobar") {
1393
+ return {linethickness: "0pt"}
1394
+ }
1395
+ return {}
1396
+ }
1397
+
1398
+ /**
1399
+ * Replace spaces with non-breaking spaces
1400
+ * @param {string} str - The string to process
1401
+ * @return {string} String with non-breaking spaces
1402
+ */
1403
+ function nbsp(str) {
1404
+ if (!str) {
1405
+ return ""
1406
+ }
1407
+ return str.replace(/\s/g, "\u00a0")
1408
+ }
1409
+
1410
+ /**
1411
+ * Parse a boolean value
1412
+ * @param {string} str - The string to parse
1413
+ * @return {boolean|undefined} The parsed boolean or undefined
1414
+ */
1415
+ function tf(str) {
1416
+ if (str == null) {
1417
+ return
1418
+ }
1419
+ str = str.toLowerCase()
1420
+ if (str === "on" || str === "1" || str === "true") {
1421
+ return true
1422
+ }
1423
+ if (str === "off" || str === "0" || str === "false") {
1424
+ return false
1425
+ }
1426
+ }
1427
+
1428
+ /**
1429
+ * Force a value to be true unless explicitly false
1430
+ * @param {string} str - The string to parse
1431
+ * @return {boolean} True unless the string is explicitly false
1432
+ */
1433
+ function forceFalse(str) {
1434
+ const res = tf(str)
1435
+ if (res === false) {
1436
+ return false
1437
+ }
1438
+ return true
1439
+ }
1440
+
1441
+ /**
1442
+ * Force a value to be false unless explicitly true
1443
+ * @param {string} str - The string to parse
1444
+ * @return {boolean} False unless the string is explicitly true
1445
+ */
1446
+ function forceTrue(str) {
1447
+ return tf(str) || false
1448
+ }