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