ed-mathml2tex 0.0.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.
@@ -0,0 +1,1025 @@
1
+ const Brackets = {
2
+ left: ['(', '[', '{', '|', '‖', '⟨', '⌊', '⌈', '⌜'],
3
+ right: [')', ']', '}', '|', '‖', '⟩', '⌋', '⌉', '⌝'],
4
+ isPair: function(l, r){
5
+ const idx = this.left.indexOf(l);
6
+ return r === this.right[idx];
7
+ },
8
+ contains: function(it) {
9
+ return this.isLeft(it) || this.isRight(it);
10
+ },
11
+ isLeft: function(it) {
12
+ return this.left.indexOf(it) > -1
13
+ },
14
+ isRight: function(it) {
15
+ return this.right.indexOf(it) > -1;
16
+ },
17
+ parseLeft: function(it, stretchy = true) {
18
+ if(this.left.indexOf(it) < 0){ return it}
19
+ let r = '';
20
+ switch(it){
21
+ case '(':
22
+ case '[':
23
+ case '|': r = `\\left${it}`;
24
+ break;
25
+ case '‖': r = '\\left\\|';
26
+ break;
27
+ case '{': r = '\\left\\{';
28
+ break;
29
+ case '⟨': r = '\\left\\langle ';
30
+ break;
31
+ case '⌊': r = '\\left\\lfloor ';
32
+ break;
33
+ case '⌈': r = '\\left\\lceil ';
34
+ break;
35
+ case '⌜': r = '\\left\\ulcorner ';
36
+ break;
37
+ }
38
+ return (stretchy ? r : r.replace('\\left', ''));
39
+ },
40
+
41
+ parseRight: function(it, stretchy = true) {
42
+ if(this.right.indexOf(it) < 0){ return it}
43
+ let r = '';
44
+ switch(it){
45
+ case ')':
46
+ case ']':
47
+ case '|': r = `\\right${it}`;
48
+ break;
49
+ case '‖': r = '\\right\\|';
50
+ break;
51
+ case '}': r = '\\right\\}';
52
+ break;
53
+ case '⟩': r = ' \\right\\rangle';
54
+ break;
55
+ case '⌋': r = ' \\right\\rfloor';
56
+ break;
57
+ case '⌉': r = ' \\right\\rceil';
58
+ break;
59
+ case '⌝': r = ' \\right\\urcorner';
60
+ break;
61
+ }
62
+ return (stretchy ? r : r.replace('\\right', ''));
63
+ }
64
+ };
65
+
66
+ /*
67
+ * Set up window for Node.js
68
+ */
69
+
70
+ const root = (typeof window !== 'undefined' ? window : {});
71
+
72
+ /*
73
+ * Parsing HTML strings
74
+ */
75
+
76
+ function canParseHTMLNatively () {
77
+ const Parser = root.DOMParser;
78
+ let canParse = false;
79
+
80
+ // Adapted from https://gist.github.com/1129031
81
+ // Firefox/Opera/IE throw errors on unsupported types
82
+ try {
83
+ // WebKit returns null on unsupported types
84
+ if (new Parser().parseFromString('', 'text/html')) {
85
+ canParse = true;
86
+ }
87
+ } catch (e) {}
88
+
89
+ return canParse
90
+ }
91
+
92
+ function createHTMLParser () {
93
+ const Parser = function () {};
94
+
95
+ if (typeof process !== 'undefined' && false) {
96
+ if (shouldUseActiveX()) {
97
+ Parser.prototype.parseFromString = function (string) {
98
+ const doc = new window.ActiveXObject('htmlfile');
99
+ doc.designMode = 'on'; // disable on-page scripts
100
+ doc.open();
101
+ doc.write(string);
102
+ doc.close();
103
+ return doc
104
+ };
105
+ } else {
106
+ Parser.prototype.parseFromString = function (string) {
107
+ const doc = document.implementation.createHTMLDocument('');
108
+ doc.open();
109
+ doc.write(string);
110
+ doc.close();
111
+ return doc
112
+ };
113
+ }
114
+ } else {
115
+ Parser.prototype.parseFromString = function (string) {
116
+ const doc = document.implementation.createHTMLDocument('');
117
+ doc.open();
118
+ doc.write(string);
119
+ doc.close();
120
+ return doc
121
+ };
122
+ }
123
+ return Parser
124
+ }
125
+
126
+ function shouldUseActiveX () {
127
+ let useActiveX = false;
128
+ try {
129
+ document.implementation.createHTMLDocument('').open();
130
+ } catch (e) {
131
+ if (window.ActiveXObject) useActiveX = true;
132
+ }
133
+ return useActiveX
134
+ }
135
+
136
+ const HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
137
+
138
+ const NodeTool = {
139
+ parseMath: function(html) {
140
+ const parser = new HTMLParser();
141
+ const doc = parser.parseFromString(html, 'text/html');
142
+ return doc.querySelector('math');
143
+ },
144
+ getChildren: function(node) {
145
+ return node.children;
146
+ },
147
+ getNodeName: function(node) {
148
+ return node.tagName.toLowerCase();
149
+ },
150
+ getNodeText: function(node) {
151
+ return node.textContent;
152
+ },
153
+ getAttr: function(node, attrName, defaultValue) {
154
+ const value = node.getAttribute(attrName);
155
+ if ( value === null) {
156
+ return defaultValue;
157
+ } else {
158
+ return value;
159
+ }
160
+ },
161
+ getPrevNode: function(node) {
162
+ return node.previousElementSibling;
163
+ },
164
+ getNextNode: function(node) {
165
+ return node.nextElementSibling;
166
+ }
167
+ };
168
+
169
+ // @see https://en.wikibooks.org/wiki/LaTeX/Mathematics#List_of_mathematical_symbols
170
+ // @see https://www.andy-roberts.net/res/writing/latex/symbols.pdf (more completed)
171
+ // @see http://www.rpi.edu/dept/arc/training/latex/LaTeX_symbols.pdf (wtf)
172
+ // https://oeis.org/wiki/List_of_LaTeX_mathematical_symbols
173
+
174
+ // accessed directly from keyboard
175
+ // + - = ! / ( ) [ ] < > | ' : *
176
+
177
+ const MathSymbol = {
178
+ parseIdentifier: function(it) {
179
+ if(it.length === 0){ return '' }
180
+ if(it.length === 1){
181
+ const charCode = it.charCodeAt(0);
182
+ // First check special math sets
183
+ let index = this.mathSets.decimals.indexOf(charCode);
184
+ if (index > -1) {
185
+ return this.mathSets.scripts[index] + ' ';
186
+ }
187
+ // Then check greek letters
188
+ index = this.greekLetter.decimals.indexOf(charCode);
189
+ if (index > -1) {
190
+ return this.greekLetter.scripts[index] + ' ';
191
+ }
192
+ return it;
193
+ } else {
194
+ return this.parseMathFunction(it);
195
+ }
196
+ },
197
+
198
+ parseOperator: function(it) {
199
+ if(it.length === 0){ return ''}
200
+ // Handle backslash operator
201
+ if(it === '\\'){ return '\\setminus '}
202
+ if(it.length === 1){
203
+ const charCode = it.charCodeAt(0);
204
+ // First check special operators
205
+ let index = this.specialOperators.decimals.indexOf(charCode);
206
+ if (index > -1) {
207
+ return this.specialOperators.scripts[index] + ' ';
208
+ }
209
+
210
+ const opSymbols = [
211
+ this.bigCommand,
212
+ this.relation,
213
+ this.binaryOperation,
214
+ this.setAndLogic,
215
+ this.delimiter,
216
+ this.other
217
+ ];
218
+
219
+ const padSpaceBothSide = [false, true, true, false, false, false];
220
+
221
+ for(let i = 0; i < opSymbols.length; i++){
222
+ const opSymbol = opSymbols[i];
223
+ const index = opSymbol.decimals.indexOf(charCode);
224
+ if(index > -1) {
225
+ if(padSpaceBothSide[i]){
226
+ return [' ', opSymbol.scripts[index], ' '].join('');
227
+ }else {
228
+ return opSymbol.scripts[index] + ' ';
229
+ }
230
+ }
231
+ }
232
+ return it;
233
+ } else {
234
+ return this.parseMathFunction(it);
235
+ }
236
+ },
237
+
238
+ parseMathFunction: function (it) {
239
+ const marker = T.createMarker();
240
+ const replacements = [];
241
+ this.mathFunction.names.forEach((name, index) => {
242
+ const regExp = new RegExp(name, 'g');
243
+ if(it.match(regExp)){
244
+ replacements.push(this.mathFunction.scripts[index]);
245
+ it = it.replace(regExp, marker.next() + ' ');
246
+ }
247
+ });
248
+ return marker.replaceBack(it, replacements);
249
+ },
250
+
251
+ //FIXME COMPLETE ME
252
+ overScript: {
253
+ decimals: [9182, 8594],
254
+ templates: [
255
+ "\\overbrace{@v}",
256
+ "\\vec{@v}"
257
+ ]
258
+ },
259
+
260
+ //FIXME COMPLETE ME
261
+ underScript: {
262
+ decimals: [9183],
263
+ templates: [
264
+ "\\underbrace{@v}"
265
+ ]
266
+ },
267
+
268
+ // sum, integral...
269
+ bigCommand: {
270
+ decimals: [8721, 8719, 8720, 10753, 10754, 10752, 8899, 8898, 10756, 10758, 8897, 8896, 8747, 8750, 8748, 8749, 10764, 8747],
271
+ scripts: [
272
+ "\\sum",
273
+ "\\prod",
274
+ "\\coprod",
275
+ "\\bigoplus",
276
+ "\\bigotimes",
277
+ "\\bigodot",
278
+ "\\bigcup",
279
+ "\\bigcap",
280
+ "\\biguplus",
281
+ "\\bigsqcup",
282
+ "\\bigvee",
283
+ "\\bigwedge",
284
+ "\\int",
285
+ "\\oint",
286
+ "\\iint",
287
+ "\\iiint",
288
+ "\\iiiint",
289
+ "\\idotsint",
290
+ ]
291
+ },
292
+
293
+ // mo
294
+ relation: {
295
+ decimals: [60, 62, 61, 8741, 8742, 8804, 8805, 8784, 8781, 8904, 8810, 8811, 8801, 8866, 8867, 8834, 8835, 8776, 8712, 8715, 8838, 8839, 8773, 8995, 8994, 8840, 8841, 8771, 8872, 8713, 8847, 8848, 126, 8764, 8869, 8739, 8849, 8850, 8733, 8826, 8827, 10927, 10928, 8800, 8738, 8737],
296
+ scripts: [
297
+ "<",
298
+ ">",
299
+ "=",
300
+ "\\parallel",
301
+ "\\nparallel",
302
+ "\\leq",
303
+ "\\geq",
304
+ "\\doteq",
305
+ "\\asymp",
306
+ "\\bowtie",
307
+ "\\ll",
308
+ "\\gg",
309
+ "\\equiv",
310
+ "\\vdash",
311
+ "\\dashv",
312
+ "\\subset",
313
+ "\\supset",
314
+ "\\approx",
315
+ "\\in",
316
+ "\\ni",
317
+ "\\subseteq",
318
+ "\\supseteq",
319
+ "\\cong",
320
+ "\\smile",
321
+ "\\frown",
322
+ "\\nsubseteq",
323
+ "\\nsupseteq",
324
+ "\\simeq",
325
+ "\\models",
326
+ "\\notin",
327
+ "\\sqsubset",
328
+ "\\sqsupset",
329
+ "\\sim",
330
+ "\\sim",
331
+ "\\perp",
332
+ "\\mid",
333
+ "\\sqsubseteq",
334
+ "\\sqsupseteq",
335
+ "\\propto",
336
+ "\\prec",
337
+ "\\succ",
338
+ "\\preceq",
339
+ "\\succeq",
340
+ "\\neq",
341
+ "\\sphericalangle",
342
+ "\\measuredangle"
343
+ ]
344
+ },
345
+
346
+ // complete
347
+ binaryOperation: {
348
+ decimals: [43, 45, 177, 8745, 8900, 8853, 8723, 8746, 9651, 8854, 215, 8846, 9661, 8855, 247, 8851, 9667, 8856, 8727, 8852, 9657, 8857, 8902, 8744, 9711, 8728, 8224, 8743, 8729, 8726, 8225, 8901, 8768, 10815],
349
+ scripts: [
350
+ "+",
351
+ "-",
352
+ "\\pm",
353
+ "\\cap",
354
+ "\\diamond",
355
+ "\\oplus",
356
+ "\\mp",
357
+ "\\cup",
358
+ "\\bigtriangleup",
359
+ "\\ominus",
360
+ "\\times",
361
+ "\\uplus",
362
+ "\\bigtriangledown",
363
+ "\\otimes",
364
+ "\\div",
365
+ "\\sqcap",
366
+ "\\triangleleft",
367
+ "\\oslash",
368
+ "\\ast",
369
+ "\\sqcup",
370
+ "\\triangleright",
371
+ "\\odot",
372
+ "\\star",
373
+ "\\vee",
374
+ "\\bigcirc",
375
+ "\\circ",
376
+ "\\dagger",
377
+ "\\wedge",
378
+ "\\bullet",
379
+ "\\setminus",
380
+ "\\ddagger",
381
+ "\\cdot",
382
+ "\\wr",
383
+ "\\amalg"
384
+ ]
385
+ },
386
+
387
+ setAndLogic: {
388
+ decimals: [8707, 8594, 8594, 8708, 8592, 8592, 8704, 8614, 172, 10233, 8834, 8658, 10233, 8835, 8596, 8712, 10234, 8713, 8660, 8715, 8868, 8743, 8869, 8744, 8709, 8709],
389
+ scripts: [
390
+ "\\exists",
391
+ "\\rightarrow",
392
+ "\\to",
393
+ "\\nexists",
394
+ "\\leftarrow",
395
+ "\\gets",
396
+ "\\forall",
397
+ "\\mapsto",
398
+ "\\neg",
399
+ "\\implies",
400
+ "\\subset",
401
+ "\\Rightarrow",
402
+ "\\implies",
403
+ "\\supset",
404
+ "\\leftrightarrow",
405
+ "\\in",
406
+ "\\iff",
407
+ "\\notin",
408
+ "\\Leftrightarrow",
409
+ "\\ni",
410
+ "\\top",
411
+ "\\land",
412
+ "\\bot",
413
+ "\\lor",
414
+ "\\emptyset",
415
+ "\\varnothing"
416
+ ]
417
+ },
418
+
419
+ delimiter: {
420
+ decimals: [124, 8739, 8214, 47, 8726, 123, 125, 10216, 10217, 8593, 8657, 8968, 8969, 8595, 8659, 8970, 8971],
421
+ scripts: [
422
+ "|",
423
+ "\\mid",
424
+ "\\|",
425
+ "/",
426
+ "\\backslash",
427
+ "\\{",
428
+ "\\}",
429
+ "\\langle",
430
+ "\\rangle",
431
+ "\\uparrow",
432
+ "\\Uparrow",
433
+ "\\lceil",
434
+ "\\rceil",
435
+ "\\downarrow",
436
+ "\\Downarrow",
437
+ "\\lfloor",
438
+ "\\rfloor"
439
+ ]
440
+ },
441
+
442
+ greekLetter: {
443
+ decimals: [ 913, 945, 925, 957, 914, 946, 926, 958, 915, 947, 927, 959, 916, 948, 928, 960, 982, 917, 1013, 949, 929, 961, 1009, 918, 950, 931, 963, 962, 919, 951, 932, 964, 920, 952, 977, 933, 965, 921, 953, 934, 981, 966, 922, 954, 1008, 935, 967, 923, 955, 936, 968, 924, 956, 937, 969 ],
444
+ scripts: [
445
+ "A" , "\\alpha" ,
446
+ "N" , "\\nu" ,
447
+ "B" , "\\beta" ,
448
+ "\\Xi" , "\\xi" ,
449
+ "\\Gamma" , "\\gamma" ,
450
+ "O" , "o" ,
451
+ "\\Delta" , "\\delta" ,
452
+ "\\Pi" , "\\pi" , "\\varpi" ,
453
+ "E" , "\\epsilon" , "\\varepsilon" ,
454
+ "P" , "\\rho" , "\\varrho" ,
455
+ "Z" , "\\zeta" ,
456
+ "\\Sigma" , "\\sigma" , "\\varsigma" ,
457
+ "H" , "\\eta" ,
458
+ "T" , "\\tau" ,
459
+ "\\Theta" , "\\theta" , "\\vartheta" ,
460
+ "\\Upsilon" , "\\upsilon" ,
461
+ "I" , "\\iota" ,
462
+ "\\Phi" , "\\phi" , "\\varphi" ,
463
+ "K" , "\\kappa" , "\\varkappa" ,
464
+ "X" , "\\chi" ,
465
+ "\\Lambda" , "\\lambda" ,
466
+ "\\Psi" , "\\psi" ,
467
+ "M" , "\\mu" ,
468
+ "\\Omega" , "\\omega"
469
+ ]
470
+ },
471
+
472
+ other: {
473
+ decimals: [8706, 305, 8476, 8711, 8501, 240, 567, 8465, 9723, 8502, 8463, 8467, 8472, 8734, 8503],
474
+ scripts: [
475
+ "\\partial",
476
+ "\\imath",
477
+ "\\Re",
478
+ "\\nabla",
479
+ "\\aleph",
480
+ "\\eth",
481
+ "\\jmath",
482
+ "\\Im",
483
+ "\\Box",
484
+ "\\beth",
485
+ "\\hbar",
486
+ "\\ell",
487
+ "\\wp",
488
+ "\\infty",
489
+ "\\gimel"
490
+ ]
491
+ },
492
+
493
+ // complete
494
+ // Be careful, the order of these name matters (overlap situation).
495
+ mathFunction: {
496
+
497
+ names: [
498
+ "arcsin" , "sinh" , "sin" , "sec" ,
499
+ "arccos" , "cosh" , "cos" , "csc" ,
500
+ "arctan" , "tanh" , "tan" ,
501
+ "arccot" , "coth" , "cot" ,
502
+
503
+ "limsup" , "liminf" , "exp" , "ker" ,
504
+ "deg" , "gcd" , "lg" , "ln" ,
505
+ "Pr" , "sup" , "det" , "hom" ,
506
+ "lim" , "log" , "arg" , "dim" ,
507
+ "inf" , "max" , "min" ,
508
+ ],
509
+ scripts: [
510
+ "\\arcsin" , "\\sinh" , "\\sin" , "\\sec" ,
511
+ "\\arccos" , "\\cosh" , "\\cos" , "\\csc" ,
512
+ "\\arctan" , "\\tanh" , "\\tan" ,
513
+ "\\arccot" , "\\coth" , "\\cot" ,
514
+
515
+ "\\limsup" , "\\liminf" , "\\exp" , "\\ker" ,
516
+ "\\deg" , "\\gcd" , "\\lg" , "\\ln" ,
517
+ "\\Pr" , "\\sup" , "\\det" , "\\hom" ,
518
+ "\\lim" , "\\log" , "\\arg" , "\\dim" ,
519
+ "\\inf" , "\\max" , "\\min" ,
520
+ ]
521
+ },
522
+
523
+ // Add special operators section
524
+ specialOperators: {
525
+ decimals: [
526
+ 8712, // ∈
527
+ 8713, // ∉
528
+ 8746, // ∪
529
+ 8745, // ∩
530
+ 92, // \
531
+ ],
532
+ scripts: [
533
+ "\\in",
534
+ "\\notin",
535
+ "\\cup",
536
+ "\\cap",
537
+ "\\setminus"
538
+ ]
539
+ },
540
+
541
+ // Update mathSets section
542
+ mathSets: {
543
+ decimals: [
544
+ 8477, // ℝ
545
+ 8484, // ℤ
546
+ 8469, // ℕ
547
+ 8474, // ℚ
548
+ 8450, // ℂ
549
+ 8709, // ∅
550
+ 8734 // ∞
551
+ ],
552
+ scripts: [
553
+ "\\mathbb{R}",
554
+ "\\mathbb{Z}",
555
+ "\\mathbb{N}",
556
+ "\\mathbb{Q}",
557
+ "\\mathbb{C}",
558
+ "\\emptyset",
559
+ "\\infty"
560
+ ]
561
+ }
562
+ };
563
+
564
+ const T = {}; // Tool
565
+ T.createMarker = function() {
566
+ return {
567
+ idx: -1,
568
+ reReplace: /@\[\[(\d+)\]\]/mg,
569
+ next: function() {
570
+ return `@[[${++this.idx}]]`
571
+ },
572
+ replaceBack: function(str, replacements) {
573
+ return str.replace(this.reReplace, (match, p1) => {
574
+ const index = parseInt(p1);
575
+ return replacements[index];
576
+ });
577
+ }
578
+ }
579
+ };
580
+
581
+ function convert(mathmlHtml){
582
+ const math = NodeTool.parseMath(mathmlHtml);
583
+ return toLatex(parse(math));
584
+ }
585
+
586
+ function toLatex(result) {
587
+ // binomial coefficients
588
+ result = result.replace(/\\left\(\\DELETE_BRACKET_L/g, '');
589
+ result = result.replace(/\\DELETE_BRACKET_R\\right\)/g, '');
590
+ result = result.replace(/\\DELETE_BRACKET_L/g, '');
591
+ result = result.replace(/\\DELETE_BRACKET_R/g, '');
592
+ return result;
593
+ }
594
+
595
+ function parse(node) {
596
+ const children = NodeTool.getChildren(node);
597
+ if (!children || children.length === 0) {
598
+ return parseLeaf(node);
599
+ } else {
600
+ return parseContainer(node, children);
601
+ }
602
+ }
603
+
604
+ // @see https://www.w3.org/TR/MathML3/chapter7.html
605
+ function parseLeaf(node) {
606
+ let r = '';
607
+ const nodeName = NodeTool.getNodeName(node);
608
+ switch(nodeName){
609
+ case 'mi': r = parseElementMi(node);
610
+ break;
611
+ case 'mn': r = parseElementMn(node);
612
+ break;
613
+ case 'mo': r = parseOperator(node);
614
+ break;
615
+ case 'ms': r = parseElementMs(node);
616
+ break;
617
+ case 'mtext': r = parseElementMtext(node);
618
+ break;
619
+ case 'mglyph': r = parseElementMglyph(node);
620
+ break;
621
+ case 'mprescripts': r = '';
622
+ break;
623
+ case 'mspace': r = parseElementMspace();
624
+ case 'none': r = '\\:';
625
+ //TODO other usecase of 'none' ?
626
+ break;
627
+ default: r = escapeSpecialChars(NodeTool.getNodeText(node).trim());
628
+ break;
629
+ }
630
+ return r;
631
+ }
632
+
633
+ // operator token, mathematical operators
634
+ function parseOperator(node) {
635
+ let it = NodeTool.getNodeText(node).trim();
636
+ it = MathSymbol.parseOperator(it);
637
+ return escapeSpecialChars(it);
638
+ }
639
+
640
+ // Math identifier
641
+ function parseElementMi(node){
642
+ let it = NodeTool.getNodeText(node).trim();
643
+ it = MathSymbol.parseIdentifier(it);
644
+ return escapeSpecialChars(it);
645
+ }
646
+
647
+ // Math Number
648
+ function parseElementMn(node){
649
+ let it = NodeTool.getNodeText(node).trim();
650
+ return escapeSpecialChars(it);
651
+ }
652
+
653
+ // Math String
654
+ function parseElementMs(node){
655
+ const content = NodeTool.getNodeText(node).trimRight();
656
+ const it = escapeSpecialChars(content);
657
+ return ['"', it, '"'].join('');
658
+ }
659
+
660
+ // Math Text
661
+ function parseElementMtext(node){
662
+ const content = NodeTool.getNodeText(node);
663
+ const it = escapeSpecialChars(content);
664
+ return `\\text{${it}}`;
665
+ }
666
+
667
+ // Math glyph (image)
668
+ function parseElementMglyph(node){
669
+ const it = ['"', NodeTool.getAttr(node, 'alt', ''), '"'].join('');
670
+ return escapeSpecialChars(it);
671
+ }
672
+
673
+ // TODO need or not
674
+ function parseElementMspace(node){
675
+ return '';
676
+ }
677
+
678
+ function escapeSpecialChars(text) {
679
+ const specialChars = /\$|%|_|&|#|\{|\}/g;
680
+ text = text.replace(specialChars, char => `\\${ char }`);
681
+ return text;
682
+ }
683
+
684
+
685
+ function parseContainer(node, children) {
686
+ const render = getRender(node);
687
+ if(render){
688
+ return render(node, children);
689
+ } else {
690
+ throw new Error(`Couldn't get render function for container node: ${NodeTool.getNodeName(node)}`);
691
+ }
692
+ }
693
+
694
+ function renderChildren(children) {
695
+ const parts = [];
696
+ let lefts = [];
697
+ Array.prototype.forEach.call(children, (node) => {
698
+ if(NodeTool.getNodeName(node) === 'mo'){
699
+ const op = NodeTool.getNodeText(node).trim();
700
+ if(Brackets.contains(op)){
701
+ let stretchy = NodeTool.getAttr(node, 'stretchy', 'true');
702
+ stretchy = ['', 'true'].indexOf(stretchy) > -1;
703
+ // 操作符是括號
704
+ if(Brackets.isRight(op)){
705
+ const nearLeft = lefts[lefts.length - 1];
706
+ if(nearLeft){
707
+ if(Brackets.isPair(nearLeft, op)){
708
+ parts.push(Brackets.parseRight(op, stretchy));
709
+ lefts.pop();
710
+ } else {
711
+ // some brackets left side is same as right side.
712
+ if(Brackets.isLeft(op)) {
713
+ parts.push(Brackets.parseLeft(op, stretchy));
714
+ lefts.push(op);
715
+ } else {
716
+ console.error("bracket not match");
717
+ }
718
+ }
719
+ }else {
720
+ // some brackets left side is same as right side.
721
+ if(Brackets.isLeft(op)) {
722
+ parts.push(Brackets.parseLeft(op, stretchy));
723
+ lefts.push(op);
724
+ }else {
725
+ console.error("bracket not match");
726
+ }
727
+ }
728
+ } else {
729
+ parts.push(Brackets.parseLeft(op, stretchy));
730
+ lefts.push(op);
731
+ }
732
+ } else {
733
+ parts.push(parseOperator(node));
734
+ }
735
+ } else {
736
+ parts.push(parse(node));
737
+ }
738
+ });
739
+ // 這裏非常不嚴謹
740
+ if(lefts.length > 0){
741
+ for(let i=0; i < lefts.length; i++){
742
+ parts.push("\\right.");
743
+ }
744
+ }
745
+ lefts = undefined;
746
+ return parts;
747
+ }
748
+
749
+
750
+ function getRender(node) {
751
+ let render = undefined;
752
+ const nodeName = NodeTool.getNodeName(node);
753
+ switch(nodeName){
754
+ case 'msub':
755
+ render = getRender_default("@1_{@2}");
756
+ break;
757
+ case 'msup':
758
+ render = getRender_default("@1^{@2}");
759
+ break;
760
+ case 'msubsup':
761
+ render = getRender_default("@1_{@2}^{@3}");
762
+ break;
763
+ case 'mover':
764
+ render = renderMover;
765
+ break;
766
+ case 'munder':
767
+ render = renderMunder;
768
+ break;
769
+ case 'munderover':
770
+ render = getRender_default("@1\\limits_{@2}^{@3}");
771
+ break;
772
+ case 'mmultiscripts':
773
+ render = renderMmultiscripts;
774
+ break;
775
+ case 'mroot':
776
+ render = getRender_default("\\sqrt[@2]{@1}");
777
+ break;
778
+ case 'msqrt':
779
+ render = getRender_joinSeparator("\\sqrt{@content}");
780
+ break;
781
+ case 'mtable':
782
+ render = renderTable;
783
+ break;
784
+ case 'mtr':
785
+ render = getRender_joinSeparator("@content \\\\ ", ' & ');
786
+ break;
787
+ case 'mtd':
788
+ render = getRender_joinSeparator("@content");
789
+ break;
790
+ case 'mfrac':
791
+ render = renderMfrac;
792
+ break;
793
+ case 'mfenced':
794
+ render = renderMfenced;
795
+ break;
796
+ case 'mi':
797
+ case 'mn':
798
+ case 'mo':
799
+ case 'ms':
800
+ case 'mtext':
801
+ // they may contains <mglyph>
802
+ render = getRender_joinSeparator("@content");
803
+ break;
804
+ case 'mphantom':
805
+ render = renderMphantom;
806
+ break;
807
+ default:
808
+ // math, mstyle, mrow
809
+ render = getRender_joinSeparator("@content");
810
+ break;
811
+ }
812
+ return render;
813
+ }
814
+
815
+ function renderTable(node, children) {
816
+ const template = "\\begin{matrix} @content \\end{matrix}";
817
+ const render = getRender_joinSeparator(template);
818
+ return render(node, children);
819
+ }
820
+
821
+ function renderMfrac(node, children){
822
+ const [linethickness, bevelled] = [
823
+ NodeTool.getAttr(node, 'linethickness', 'medium'),
824
+ NodeTool.getAttr(node, 'bevelled', 'false')
825
+ ];
826
+
827
+ let render = null;
828
+ if(bevelled === 'true') {
829
+ render = getRender_default("{}^{@1}/_{@2}");
830
+ } else if(['0', '0px'].indexOf(linethickness) > -1) {
831
+ const [prevNode, nextNode] = [
832
+ NodeTool.getPrevNode(node),
833
+ NodeTool.getNextNode(node)
834
+ ];
835
+ if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
836
+ (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
837
+ ) {
838
+ render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
839
+ } else {
840
+ render = getRender_default("{}_{@2}^{@1}");
841
+ }
842
+ } else {
843
+ render = getRender_default("\\frac{@1}{@2}");
844
+ }
845
+ return render(node, children);
846
+ }
847
+
848
+ function renderMfenced(node, children){
849
+ const [open, close, separatorsStr] = [
850
+ NodeTool.getAttr(node, 'open', '('),
851
+ NodeTool.getAttr(node, 'close', ')'),
852
+ NodeTool.getAttr(node, 'separators', ',')
853
+ ];
854
+ const [left, right] = [
855
+ Brackets.parseLeft(open),
856
+ Brackets.parseRight(close)
857
+ ];
858
+
859
+ const separators = separatorsStr.split('').filter((c) => c.trim().length === 1);
860
+ const template = `${left}@content${right}`;
861
+ const render = getRender_joinSeparators(template, separators);
862
+ return render(node, children);
863
+ }
864
+
865
+ function renderMmultiscripts(node, children) {
866
+ if(children.length === 0) { return '' }
867
+ let sepIndex = -1;
868
+ let mprescriptsNode = null;
869
+ Array.prototype.forEach.call(children, (node) => {
870
+ if(NodeTool.getNodeName(node) === 'mprescripts'){
871
+ mprescriptsNode = node;
872
+ }
873
+ });
874
+ if(mprescriptsNode) {
875
+ sepIndex = Array.prototype.indexOf.call(children, mprescriptsNode);
876
+ }
877
+ const parts = renderChildren(children);
878
+
879
+ const splitArray = (arr, index) => {
880
+ return [arr.slice(0, index), arr.slice(index + 1, arr.length)]
881
+ };
882
+ const renderScripts = (items) => {
883
+ if(items.length > 0) {
884
+ const subs = [];
885
+ const sups = [];
886
+ items.forEach((item, index) => {
887
+ // one render as sub script, one as super script
888
+ if((index + 1) % 2 === 0){
889
+ sups.push(item);
890
+ } else {
891
+ subs.push(item);
892
+ }
893
+ });
894
+ return [
895
+ (subs.length > 0 ? `_{${subs.join(' ')}}` : ''),
896
+ (sups.length > 0 ? `^{${sups.join(' ')}}` : '')
897
+ ].join('');
898
+ } else {
899
+ return '';
900
+ }
901
+ };
902
+ const base = parts.shift();
903
+ let prevScripts = [];
904
+ let backScripts = [];
905
+ if(sepIndex === -1){
906
+ backScripts = parts;
907
+ } else {
908
+ [backScripts, prevScripts] = splitArray(parts, sepIndex - 1);
909
+ }
910
+ return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
911
+ }
912
+
913
+ function renderMover(node, children){
914
+ const nodes = flattenNodeTreeByNodeName(node, 'mover');
915
+ let result = undefined;
916
+ for(let i = 0; i < nodes.length - 1; i++) {
917
+ if(!result){ result = parse(nodes[i]); }
918
+ const over = parse(nodes[i + 1]);
919
+ const template = getMatchValueByChar({
920
+ decimals: MathSymbol.overScript.decimals,
921
+ values: MathSymbol.overScript.templates,
922
+ judgeChar: over,
923
+ defaultValue: "@1^{@2}"
924
+ });
925
+ result = renderTemplate(template.replace("@v", "@1"), [result, over]);
926
+ }
927
+ return result;
928
+ }
929
+
930
+ function renderMunder(node, children){
931
+ const nodes = flattenNodeTreeByNodeName(node, 'munder');
932
+ let result = undefined;
933
+ for(let i = 0; i < nodes.length - 1; i++) {
934
+ if(!result){ result = parse(nodes[i]); }
935
+ const under = parse(nodes[i + 1]);
936
+ const template = getMatchValueByChar({
937
+ decimals: MathSymbol.underScript.decimals,
938
+ values: MathSymbol.underScript.templates,
939
+ judgeChar: under,
940
+ defaultValue: "@1\\limits_{@2}"
941
+ });
942
+ result = renderTemplate(template.replace("@v", "@1"), [result, under]);
943
+ }
944
+ return result;
945
+ }
946
+
947
+ function flattenNodeTreeByNodeName(root, nodeName) {
948
+ let result = [];
949
+ const children = NodeTool.getChildren(root);
950
+ Array.prototype.forEach.call(children, (node) => {
951
+ if (NodeTool.getNodeName(node) === nodeName) {
952
+ result = result.concat(flattenNodeTreeByNodeName(node, nodeName));
953
+ } else {
954
+ result.push(node);
955
+ }
956
+ });
957
+ return result;
958
+ }
959
+
960
+
961
+ function getMatchValueByChar(params) {
962
+ const {decimals, values, judgeChar, defaultValue=null} = params;
963
+ if (judgeChar && judgeChar.length === 1) {
964
+ const index = decimals.indexOf(judgeChar.charCodeAt(0));
965
+ if (index > -1) {
966
+ return values[index];
967
+ }
968
+ }
969
+ return defaultValue;
970
+ }
971
+
972
+ // https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mphantom
973
+ // FIXME :)
974
+ function renderMphantom(node, children) {
975
+ return '';
976
+ }
977
+
978
+
979
+
980
+ function getRender_default(template) {
981
+ return function(node, children) {
982
+ const parts = renderChildren(children);
983
+ return renderTemplate(template, parts)
984
+ }
985
+ }
986
+
987
+ function renderTemplate(template, values) {
988
+ return template.replace(/\@\d+/g, (m) => {
989
+ const idx = parseInt(m.substring(1, m.length)) - 1;
990
+ return values[idx];
991
+ });
992
+ }
993
+
994
+ function getRender_joinSeparator(template, separator = '') {
995
+ return function(node, children) {
996
+ const parts = renderChildren(children);
997
+ return template.replace("@content", parts.join(separator));
998
+ }
999
+ }
1000
+
1001
+ function getRender_joinSeparators(template, separators) {
1002
+ return function(node, children) {
1003
+ const parts = renderChildren(children);
1004
+ let content = '';
1005
+ if(separators.length === 0){
1006
+ content = parts.join('');
1007
+ } else {
1008
+ content = parts.reduce((accumulator, part, index) => {
1009
+ accumulator += part;
1010
+ if(index < parts.length - 1){
1011
+ accumulator += (separators[index] || separators[separators.length - 1]);
1012
+ }
1013
+ return accumulator;
1014
+ }, '');
1015
+ }
1016
+ return template.replace("@content", content);
1017
+ }
1018
+ }
1019
+
1020
+ // Add exports at the end of file
1021
+ var mathml2latex = {
1022
+ convert: convert
1023
+ };
1024
+
1025
+ export default mathml2latex;