ed-mathml2tex 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/mathml2latex.browser.cjs.js +369 -227
- package/lib/mathml2latex.browser.es.js +369 -227
- package/lib/mathml2latex.browser.umd.js +369 -227
- package/lib/mathml2latex.cjs.js +360 -227
- package/lib/mathml2latex.es.js +358 -227
- package/lib/mathml2latex.umd.js +362 -231
- package/package.json +1 -1
package/lib/mathml2latex.umd.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
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';
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('domino')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['domino'], factory) :
|
|
4
|
+
(global = global || self, global.MathML2LaTeX = factory(global.domino));
|
|
5
|
+
}(this, (function (domino) { 'use strict';
|
|
6
|
+
|
|
7
|
+
domino = domino && Object.prototype.hasOwnProperty.call(domino, 'default') ? domino['default'] : domino;
|
|
6
8
|
|
|
7
9
|
const Brackets = {
|
|
8
|
-
left: ['(', '[', '{', '|', '
|
|
9
|
-
right: [')', ']', '}', '|', '
|
|
10
|
+
left: ['(', '[', '{', '|', '\u2016', '\u27E8', '\u230A', '\u2308', '\u231C'],
|
|
11
|
+
right: [')', ']', '}', '|', '\u2016', '\u27E9', '\u230B', '\u2309', '\u231D'],
|
|
10
12
|
isPair: function(l, r){
|
|
11
13
|
const idx = this.left.indexOf(l);
|
|
12
14
|
return r === this.right[idx];
|
|
@@ -28,17 +30,17 @@
|
|
|
28
30
|
case '[':
|
|
29
31
|
case '|': r = `\\left${it}`;
|
|
30
32
|
break;
|
|
31
|
-
case '
|
|
33
|
+
case '\u2016': r = '\\left\\|';
|
|
32
34
|
break;
|
|
33
35
|
case '{': r = '\\left\\{';
|
|
34
36
|
break;
|
|
35
|
-
case '
|
|
37
|
+
case '\u27E8': r = '\\left\\langle ';
|
|
36
38
|
break;
|
|
37
|
-
case '
|
|
39
|
+
case '\u230A': r = '\\left\\lfloor ';
|
|
38
40
|
break;
|
|
39
|
-
case '
|
|
41
|
+
case '\u2308': r = '\\left\\lceil ';
|
|
40
42
|
break;
|
|
41
|
-
case '
|
|
43
|
+
case '\u231C': r = '\\left\\ulcorner ';
|
|
42
44
|
break;
|
|
43
45
|
}
|
|
44
46
|
return (stretchy ? r : r.replace('\\left', ''));
|
|
@@ -52,17 +54,17 @@
|
|
|
52
54
|
case ']':
|
|
53
55
|
case '|': r = `\\right${it}`;
|
|
54
56
|
break;
|
|
55
|
-
case '
|
|
57
|
+
case '\u2016': r = '\\right\\|';
|
|
56
58
|
break;
|
|
57
59
|
case '}': r = '\\right\\}';
|
|
58
60
|
break;
|
|
59
|
-
case '
|
|
61
|
+
case '\u27E9': r = ' \\right\\rangle';
|
|
60
62
|
break;
|
|
61
|
-
case '
|
|
63
|
+
case '\u230B': r = ' \\right\\rfloor';
|
|
62
64
|
break;
|
|
63
|
-
case '
|
|
65
|
+
case '\u2309': r = ' \\right\\rceil';
|
|
64
66
|
break;
|
|
65
|
-
case '
|
|
67
|
+
case '\u231D': r = ' \\right\\urcorner';
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
68
70
|
return (stretchy ? r : r.replace('\\right', ''));
|
|
@@ -98,11 +100,17 @@
|
|
|
98
100
|
function createHTMLParser () {
|
|
99
101
|
const Parser = function () {};
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const hasDocument =
|
|
104
|
+
typeof document !== 'undefined' &&
|
|
105
|
+
document &&
|
|
106
|
+
document.implementation &&
|
|
107
|
+
typeof document.implementation.createHTMLDocument === 'function';
|
|
108
|
+
|
|
109
|
+
if (hasDocument) {
|
|
110
|
+
if (typeof process !== 'undefined' && false && shouldUseActiveX()) {
|
|
103
111
|
Parser.prototype.parseFromString = function (string) {
|
|
104
112
|
const doc = new window.ActiveXObject('htmlfile');
|
|
105
|
-
doc.designMode = 'on';
|
|
113
|
+
doc.designMode = 'on';
|
|
106
114
|
doc.open();
|
|
107
115
|
doc.write(string);
|
|
108
116
|
doc.close();
|
|
@@ -119,11 +127,9 @@
|
|
|
119
127
|
}
|
|
120
128
|
} else {
|
|
121
129
|
Parser.prototype.parseFromString = function (string) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
doc.close();
|
|
126
|
-
return doc
|
|
130
|
+
const domino$1 = domino;
|
|
131
|
+
const window = domino$1.createWindow(string || '');
|
|
132
|
+
return window.document
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
135
|
return Parser
|
|
@@ -141,11 +147,54 @@
|
|
|
141
147
|
|
|
142
148
|
const HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
|
|
143
149
|
|
|
150
|
+
function normalizeMathMLInput(input) {
|
|
151
|
+
let s = input == null ? '' : String(input);
|
|
152
|
+
|
|
153
|
+
if (s.includes('<math') && /\\[ntr"]/.test(s)) {
|
|
154
|
+
s = s
|
|
155
|
+
.replace(/\\r\\n/g, '\n')
|
|
156
|
+
.replace(/\\n/g, '\n')
|
|
157
|
+
.replace(/\\t/g, '\t')
|
|
158
|
+
.replace(/\\"/g, '"')
|
|
159
|
+
.replace(/\\'/g, "'");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
s = s.replace(/^\s*<\?xml[\s\S]*?\?>\s*/i, '');
|
|
163
|
+
|
|
164
|
+
s = s.replace(
|
|
165
|
+
/xmlns\s*=\s*(["'])\s*`?\s*(https?:\/\/www\.w3\.org\/1998\/Math\/MathML)\s*`?\s*\1/gi,
|
|
166
|
+
'xmlns="$2"'
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
s = s.replace(/xmlns\s*=\s*(["'])\s*`?\s*http:\/\/www\.w3\.org\/1998\/Math\/MathML\\`?\s*\1/gi, 'xmlns="http://www.w3.org/1998/Math/MathML"');
|
|
170
|
+
|
|
171
|
+
if (!/<math\b/i.test(s) && /<(msub|mrow|mi|mn|mo|mfrac|msup|msubsup|munder|mover|munderover|mtable)\b/i.test(s)) {
|
|
172
|
+
s = `<math xmlns="http://www.w3.org/1998/Math/MathML">${s}</math>`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return s;
|
|
176
|
+
}
|
|
177
|
+
|
|
144
178
|
const NodeTool = {
|
|
145
179
|
parseMath: function(html) {
|
|
180
|
+
const normalized = normalizeMathMLInput(html);
|
|
146
181
|
const parser = new HTMLParser();
|
|
147
|
-
const doc = parser.parseFromString(
|
|
148
|
-
|
|
182
|
+
const doc = parser.parseFromString(normalized, 'text/html');
|
|
183
|
+
let math = doc && doc.querySelector ? doc.querySelector('math') : null;
|
|
184
|
+
|
|
185
|
+
if (!math) {
|
|
186
|
+
const match = normalized.match(/<math\b[\s\S]*?<\/math>/i);
|
|
187
|
+
if (match) {
|
|
188
|
+
const retryDoc = parser.parseFromString(match[0], 'text/html');
|
|
189
|
+
math = retryDoc && retryDoc.querySelector ? retryDoc.querySelector('math') : null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!math) {
|
|
194
|
+
throw new Error('Invalid MathML: missing <math> root element');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return math;
|
|
149
198
|
},
|
|
150
199
|
getChildren: function(node) {
|
|
151
200
|
return node.children;
|
|
@@ -281,24 +330,24 @@
|
|
|
281
330
|
bigCommand: {
|
|
282
331
|
decimals: [8721, 8719, 8720, 10753, 10754, 10752, 8899, 8898, 10756, 10758, 8897, 8896, 8747, 8750, 8748, 8749, 10764, 8747],
|
|
283
332
|
scripts: [
|
|
284
|
-
"\\sum",
|
|
285
|
-
"\\prod",
|
|
286
|
-
"\\coprod",
|
|
287
|
-
"\\bigoplus",
|
|
288
|
-
"\\bigotimes",
|
|
289
|
-
"\\bigodot",
|
|
290
|
-
"\\bigcup",
|
|
291
|
-
"\\bigcap",
|
|
292
|
-
"\\biguplus",
|
|
293
|
-
"\\bigsqcup",
|
|
294
|
-
"\\bigvee",
|
|
295
|
-
"\\bigwedge",
|
|
296
|
-
"\\int",
|
|
297
|
-
"\\oint",
|
|
298
|
-
"\\iint",
|
|
299
|
-
"\\iiint",
|
|
300
|
-
"\\iiiint",
|
|
301
|
-
"\\idotsint",
|
|
333
|
+
"\\sum ",
|
|
334
|
+
"\\prod ",
|
|
335
|
+
"\\coprod ",
|
|
336
|
+
"\\bigoplus ",
|
|
337
|
+
"\\bigotimes ",
|
|
338
|
+
"\\bigodot ",
|
|
339
|
+
"\\bigcup ",
|
|
340
|
+
"\\bigcap ",
|
|
341
|
+
"\\biguplus ",
|
|
342
|
+
"\\bigsqcup ",
|
|
343
|
+
"\\bigvee ",
|
|
344
|
+
"\\bigwedge ",
|
|
345
|
+
"\\int ",
|
|
346
|
+
"\\oint ",
|
|
347
|
+
"\\iint ",
|
|
348
|
+
"\\iiint ",
|
|
349
|
+
"\\iiiint ",
|
|
350
|
+
"\\idotsint ",
|
|
302
351
|
]
|
|
303
352
|
},
|
|
304
353
|
|
|
@@ -616,9 +665,10 @@
|
|
|
616
665
|
// Thêm xử lý cho các thẻ MathML khác
|
|
617
666
|
result = result
|
|
618
667
|
.replace(/∞/g, "\\infty") // Vô cực
|
|
619
|
-
.replace(
|
|
620
|
-
|
|
621
|
-
|
|
668
|
+
.replace(/∫/g, " \\int "); // Tích phân
|
|
669
|
+
|
|
670
|
+
// Đảm bảo dấu cách sau các lệnh LaTeX quan trọng để tránh dính biến (vd: \intf -> \int f)
|
|
671
|
+
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim(?!its)|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
622
672
|
|
|
623
673
|
return result;
|
|
624
674
|
}
|
|
@@ -764,20 +814,24 @@
|
|
|
764
814
|
"±": " \\pm ",
|
|
765
815
|
"×": " \\times ",
|
|
766
816
|
"÷": " \\div ",
|
|
767
|
-
"∑": "
|
|
768
|
-
"∏": "
|
|
769
|
-
"∫": "\\int",
|
|
817
|
+
"∑": "\\sum ",
|
|
818
|
+
"∏": "\\prod ",
|
|
819
|
+
"∫": "\\int ",
|
|
770
820
|
"−": "-",
|
|
771
821
|
"≠": " \\neq ",
|
|
772
822
|
">": " > ",
|
|
773
823
|
"=": " = ",
|
|
824
|
+
"(": "(",
|
|
825
|
+
")": ")",
|
|
774
826
|
",": ", ", // Dấu phẩy trong tập hợp
|
|
775
827
|
";": ";",
|
|
776
828
|
Ω: "\\Omega",
|
|
777
829
|
"|": " \\mid ", // PATCH: set-builder mid
|
|
778
830
|
π: " \\pi ", // PATCH: Greek letter
|
|
831
|
+
"...": "\\dots",
|
|
779
832
|
};
|
|
780
|
-
|
|
833
|
+
const res = operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
|
|
834
|
+
return res;
|
|
781
835
|
}
|
|
782
836
|
// --- END PATCH ---
|
|
783
837
|
|
|
@@ -804,6 +858,20 @@
|
|
|
804
858
|
// Math Number
|
|
805
859
|
function parseElementMn(node) {
|
|
806
860
|
let it = NodeTool.getNodeText(node).trim();
|
|
861
|
+
// Loại bỏ các ký tự điều khiển hoặc khoảng trắng lạ
|
|
862
|
+
it = it.replace(/[\u0000-\u001F\u007F-\u009F\u00A0]/g, "");
|
|
863
|
+
|
|
864
|
+
// Danh sách các hàm toán học mở rộng
|
|
865
|
+
const mathFunctions = ["cos", "sin", "tan", "cot", "arccos", "arcsin", "arctan", "arccot", "log", "ln", "lim", "sinh", "cosh", "tanh", "sec", "csc"];
|
|
866
|
+
|
|
867
|
+
if (mathFunctions.some(fn => it.toLowerCase().includes(fn))) {
|
|
868
|
+
// Tìm hàm khớp chính xác nhất
|
|
869
|
+
for (const fn of mathFunctions) {
|
|
870
|
+
if (it.toLowerCase() === fn) {
|
|
871
|
+
return "\\" + fn + " ";
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
807
875
|
return escapeSpecialChars(it);
|
|
808
876
|
}
|
|
809
877
|
|
|
@@ -837,8 +905,8 @@
|
|
|
837
905
|
// Nếu đã là lệnh LaTeX thì giữ nguyên
|
|
838
906
|
if (content.startsWith("\\")) return content;
|
|
839
907
|
|
|
840
|
-
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH,
|
|
841
|
-
if (/^[A-Za-z][A-Za-z0-9]*$/.test(content)) {
|
|
908
|
+
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH, ..., k times)
|
|
909
|
+
if (/^[A-Za-z][A-Za-z0-9\s]*$/.test(content)) {
|
|
842
910
|
return `\\text{${content}}`;
|
|
843
911
|
}
|
|
844
912
|
|
|
@@ -893,6 +961,22 @@
|
|
|
893
961
|
return text;
|
|
894
962
|
}
|
|
895
963
|
|
|
964
|
+
function wrapBaseForScript(base) {
|
|
965
|
+
const t = (base ?? "").trim();
|
|
966
|
+
if (!t) return "";
|
|
967
|
+
if (t.startsWith("{") && t.endsWith("}")) return t;
|
|
968
|
+
if (/^\\[a-zA-Z]+$/.test(t)) return t;
|
|
969
|
+
const needsWrap =
|
|
970
|
+
t.includes("_") ||
|
|
971
|
+
t.includes("^") ||
|
|
972
|
+
/\s/.test(t) ||
|
|
973
|
+
t.startsWith("\\left") ||
|
|
974
|
+
t.startsWith("\\right") ||
|
|
975
|
+
/\\[a-zA-Z]+/.test(t) ||
|
|
976
|
+
/[{}]/.test(t);
|
|
977
|
+
return needsWrap ? `{${t}}` : t;
|
|
978
|
+
}
|
|
979
|
+
|
|
896
980
|
function parseContainer(node, children) {
|
|
897
981
|
const render = getRender(node);
|
|
898
982
|
if (render) {
|
|
@@ -911,22 +995,6 @@
|
|
|
911
995
|
// lefts là mảng object: { op: "(", index: 5 }
|
|
912
996
|
let lefts = [];
|
|
913
997
|
|
|
914
|
-
if (
|
|
915
|
-
children.length >= 3 &&
|
|
916
|
-
NodeTool.getNodeName(children[0]) === "mo" &&
|
|
917
|
-
NodeTool.getNodeText(children[0]).trim() === "{" &&
|
|
918
|
-
NodeTool.getNodeName(children[children.length - 1]) === "mo" &&
|
|
919
|
-
NodeTool.getNodeText(children[children.length - 1]).trim() === "}"
|
|
920
|
-
) {
|
|
921
|
-
// Render inner content
|
|
922
|
-
const innerContent = Array.prototype.slice
|
|
923
|
-
.call(children, 1, -1)
|
|
924
|
-
.map((child) => parse(child))
|
|
925
|
-
.join(""); // Chỉ trả về nếu nội dung không rỗng
|
|
926
|
-
const result = `\\left\\{${innerContent}\\right\\}`;
|
|
927
|
-
return result;
|
|
928
|
-
}
|
|
929
|
-
|
|
930
998
|
Array.prototype.forEach.call(children, (node, idx) => {
|
|
931
999
|
// PATCH: Thin space between variables/numbers in mfrac numerator (k 2 π)
|
|
932
1000
|
if (
|
|
@@ -954,55 +1022,59 @@
|
|
|
954
1022
|
if (Brackets.contains(op)) {
|
|
955
1023
|
let stretchy = NodeTool.getAttr(node, "stretchy", "true");
|
|
956
1024
|
stretchy = ["", "true"].indexOf(stretchy) > -1;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
|
|
1025
|
+
const parentNode = node.parentNode;
|
|
1026
|
+
const isInPower = parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
1027
|
+
if (isInPower) {
|
|
1028
|
+
stretchy = false;
|
|
961
1029
|
}
|
|
962
1030
|
|
|
963
1031
|
if (Brackets.isRight(op)) {
|
|
964
1032
|
const nearLeftObj = lefts[lefts.length - 1];
|
|
965
|
-
if (nearLeftObj) {
|
|
966
|
-
const
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
parts.
|
|
1033
|
+
if (nearLeftObj && Brackets.isPair(nearLeftObj.op, op)) {
|
|
1034
|
+
const leftIdx = nearLeftObj.index;
|
|
1035
|
+
const leftRendered =
|
|
1036
|
+
typeof leftIdx === "number" ? String(parts[leftIdx] ?? "") : "";
|
|
1037
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1038
|
+
|
|
1039
|
+
const leftRenderedTrimmed = leftRendered.trim();
|
|
1040
|
+
const rightRenderedTrimmed = String(rightRendered).trim();
|
|
1041
|
+
|
|
1042
|
+
if (
|
|
1043
|
+
leftRenderedTrimmed.startsWith("\\left") &&
|
|
1044
|
+
!rightRenderedTrimmed.startsWith("\\right")
|
|
1045
|
+
) {
|
|
1046
|
+
parts[leftIdx] = leftRendered.replace("\\left", "");
|
|
1047
|
+
} else if (
|
|
1048
|
+
!leftRenderedTrimmed.startsWith("\\left") &&
|
|
1049
|
+
rightRenderedTrimmed.startsWith("\\right")
|
|
1050
|
+
) {
|
|
1051
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
979
1052
|
}
|
|
980
|
-
|
|
1053
|
+
|
|
1054
|
+
if (rightRendered) parts.push(rightRendered);
|
|
981
1055
|
lefts.pop();
|
|
1056
|
+
} else if (Brackets.isLeft(op)) {
|
|
1057
|
+
// If it's a Right bracket but doesn't match the current Left,
|
|
1058
|
+
// AND it is also a Left bracket (e.g. '|'), treat it as a new Left.
|
|
1059
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1060
|
+
if (partToPush) parts.push(partToPush);
|
|
1061
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
982
1062
|
} else {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1063
|
+
// Unmatched right bracket
|
|
1064
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1065
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
1066
|
+
if (rightRendered) parts.push(rightRendered);
|
|
987
1067
|
}
|
|
988
1068
|
} else {
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
let partToPush = "";
|
|
995
|
-
if (stretchy && !isInPower) {
|
|
996
|
-
partToPush = `\\left${escapedOp}`;
|
|
1069
|
+
// Must be Left bracket (or only Left)
|
|
1070
|
+
if (Brackets.isLeft(op)) {
|
|
1071
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1072
|
+
if (partToPush) parts.push(partToPush);
|
|
1073
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
997
1074
|
} else {
|
|
998
|
-
|
|
1075
|
+
// Should not happen if Brackets.contains is correct
|
|
1076
|
+
if (op) parts.push(op);
|
|
999
1077
|
}
|
|
1000
|
-
if (partToPush) {
|
|
1001
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1002
|
-
parts.push(partToPush);
|
|
1003
|
-
}
|
|
1004
|
-
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1005
|
-
lefts.push({ op: op, index: parts.length - 1 });
|
|
1006
1078
|
}
|
|
1007
1079
|
} else {
|
|
1008
1080
|
const parsedOperator = parseOperator(node);
|
|
@@ -1011,45 +1083,6 @@
|
|
|
1011
1083
|
parts.push(parsedOperator);
|
|
1012
1084
|
}
|
|
1013
1085
|
}
|
|
1014
|
-
|
|
1015
|
-
// --- START PATCH V6 (Giữ nguyên logic V5) ---
|
|
1016
|
-
} else if (NodeTool.getNodeName(node) === "msub") {
|
|
1017
|
-
const subChildren = Array.from(NodeTool.getChildren(node));
|
|
1018
|
-
if (
|
|
1019
|
-
subChildren.length === 2 &&
|
|
1020
|
-
NodeTool.getNodeName(subChildren[0]) === "mo" &&
|
|
1021
|
-
NodeTool.getNodeText(subChildren[0]).trim() === ")"
|
|
1022
|
-
) {
|
|
1023
|
-
// ĐÚNG LÀ NGOẠI LỆ
|
|
1024
|
-
|
|
1025
|
-
const sub = parse(subChildren[1]);
|
|
1026
|
-
// Mảng 'parts' lúc này "sạch" và là: ["\text{Cu}", "\left(", "\text{OH}"]
|
|
1027
|
-
const lastPart = parts.pop(); // lastPart = "\text{OH}"
|
|
1028
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\left("]
|
|
1029
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1030
|
-
if (parts[i] && parts[i].trim() === "\\left(") {
|
|
1031
|
-
parts.splice(i, 1); // Xóa "\left("
|
|
1032
|
-
break;
|
|
1033
|
-
}
|
|
1034
|
-
_;
|
|
1035
|
-
} // Mảng 'parts' lúc này là: ["\text{Cu}"]
|
|
1036
|
-
|
|
1037
|
-
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1038
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1039
|
-
|
|
1040
|
-
const nearLeftObj = lefts[lefts.length - 1];
|
|
1041
|
-
if (nearLeftObj) {
|
|
1042
|
-
lefts.pop();
|
|
1043
|
-
}
|
|
1044
|
-
} else {
|
|
1045
|
-
// <msub> bình thường
|
|
1046
|
-
const parsed = parse(node);
|
|
1047
|
-
if (parsed) {
|
|
1048
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1049
|
-
parts.push(parsed);
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
// --- END PATCH V6 ---
|
|
1053
1086
|
} else {
|
|
1054
1087
|
// Các node khác như <mtext>, #text, v.v.
|
|
1055
1088
|
const parsed = parse(node);
|
|
@@ -1061,40 +1094,13 @@
|
|
|
1061
1094
|
});
|
|
1062
1095
|
|
|
1063
1096
|
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1064
|
-
//
|
|
1097
|
+
// để tránh sinh LaTeX không hợp lệ (\left mà không có \right)
|
|
1065
1098
|
if (lefts && lefts.length > 0) {
|
|
1066
|
-
const rightForLeft = (left) => {
|
|
1067
|
-
switch (left) {
|
|
1068
|
-
case "(":
|
|
1069
|
-
return ")";
|
|
1070
|
-
case "[":
|
|
1071
|
-
return "]";
|
|
1072
|
-
case "{":
|
|
1073
|
-
return "}";
|
|
1074
|
-
case "|":
|
|
1075
|
-
return "|";
|
|
1076
|
-
default:
|
|
1077
|
-
return ".";
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
1099
|
lefts.forEach((leftObj) => {
|
|
1082
1100
|
const idx = leftObj && leftObj.index;
|
|
1083
1101
|
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const right = rightForLeft(left);
|
|
1087
|
-
|
|
1088
|
-
// Kiểm tra phần tử sau vị trí left có chứa \right hoặc ký tự đóng tương ứng hay không
|
|
1089
|
-
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1090
|
-
const hasMatchingRight =
|
|
1091
|
-
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1092
|
-
|
|
1093
|
-
if (!hasMatchingRight) {
|
|
1094
|
-
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1095
|
-
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1096
|
-
}
|
|
1097
|
-
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1102
|
+
// Use regex with whitespace support and global flag just in case
|
|
1103
|
+
parts[idx] = String(parts[idx]).replace(/\\left/g, "");
|
|
1098
1104
|
});
|
|
1099
1105
|
}
|
|
1100
1106
|
|
|
@@ -1102,6 +1108,44 @@
|
|
|
1102
1108
|
return parts;
|
|
1103
1109
|
}
|
|
1104
1110
|
|
|
1111
|
+
function isLimitOperator(base) {
|
|
1112
|
+
const t = (base || "").trim();
|
|
1113
|
+
const ops = [
|
|
1114
|
+
"\\sum",
|
|
1115
|
+
"\\prod",
|
|
1116
|
+
"\\coprod",
|
|
1117
|
+
"\\int",
|
|
1118
|
+
"\\oint",
|
|
1119
|
+
"\\bigcap",
|
|
1120
|
+
"\\bigcup",
|
|
1121
|
+
"\\bigsqcup",
|
|
1122
|
+
"\\bigvee",
|
|
1123
|
+
"\\bigwedge",
|
|
1124
|
+
"\\bigodot",
|
|
1125
|
+
"\\bigotimes",
|
|
1126
|
+
"\\bigoplus",
|
|
1127
|
+
"\\biguplus",
|
|
1128
|
+
"\\lim",
|
|
1129
|
+
"\\max",
|
|
1130
|
+
"\\min",
|
|
1131
|
+
"\\sup",
|
|
1132
|
+
"\\inf",
|
|
1133
|
+
"\\det",
|
|
1134
|
+
"\\gcd",
|
|
1135
|
+
"\\Pr",
|
|
1136
|
+
"\\limsup",
|
|
1137
|
+
"\\liminf",
|
|
1138
|
+
];
|
|
1139
|
+
return ops.some(
|
|
1140
|
+
(op) =>
|
|
1141
|
+
t === op ||
|
|
1142
|
+
t.startsWith(op + " ") ||
|
|
1143
|
+
t.startsWith(op + "\\") ||
|
|
1144
|
+
t.startsWith(op + "^") ||
|
|
1145
|
+
t.startsWith(op + "_")
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1105
1149
|
function getRender(node) {
|
|
1106
1150
|
let render = undefined;
|
|
1107
1151
|
const nodeName = NodeTool.getNodeName(node);
|
|
@@ -1250,9 +1294,11 @@
|
|
|
1250
1294
|
NodeTool.getNodeText(sub.firstChild).trim() === "")
|
|
1251
1295
|
) {
|
|
1252
1296
|
const lastChild = sub.lastChild;
|
|
1253
|
-
return lastChild
|
|
1297
|
+
return lastChild
|
|
1298
|
+
? `${wrapBaseForScript(base)}_{${parse(lastChild)}}`
|
|
1299
|
+
: base;
|
|
1254
1300
|
}
|
|
1255
|
-
return `${base}_{${parse(sub)}}`;
|
|
1301
|
+
return `${wrapBaseForScript(base)}_{${parse(sub)}}`;
|
|
1256
1302
|
};
|
|
1257
1303
|
break;
|
|
1258
1304
|
|
|
@@ -1260,22 +1306,9 @@
|
|
|
1260
1306
|
render = function (node, children) {
|
|
1261
1307
|
const childrenArray = Array.from(children);
|
|
1262
1308
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1263
|
-
|
|
1264
|
-
const baseNode = childrenArray[0];
|
|
1265
|
-
let base = parse(baseNode) || "";
|
|
1266
|
-
if (NodeTool.getNodeName(baseNode) === "mo") {
|
|
1267
|
-
const op = NodeTool.getNodeText(baseNode).trim();
|
|
1268
|
-
if (Brackets.isRight(op)) {
|
|
1269
|
-
// Escape brace characters
|
|
1270
|
-
if (op === "}" || op === "{") {
|
|
1271
|
-
base = `\\right\\${op}`;
|
|
1272
|
-
} else {
|
|
1273
|
-
base = `\\right${op}`;
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1309
|
+
const base = parse(childrenArray[0]) || "";
|
|
1277
1310
|
const sup = parse(childrenArray[1]) || "";
|
|
1278
|
-
return `${base}^{${sup}}`;
|
|
1311
|
+
return `${wrapBaseForScript(base)}^{${sup}}`;
|
|
1279
1312
|
};
|
|
1280
1313
|
break;
|
|
1281
1314
|
|
|
@@ -1299,7 +1332,7 @@
|
|
|
1299
1332
|
.join("");
|
|
1300
1333
|
return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
|
|
1301
1334
|
}
|
|
1302
|
-
return `${base}_{${sub}}^{${sup}}`;
|
|
1335
|
+
return `${wrapBaseForScript(base)}_{${sub}}^{${sup}}`;
|
|
1303
1336
|
};
|
|
1304
1337
|
break;
|
|
1305
1338
|
|
|
@@ -1309,9 +1342,15 @@
|
|
|
1309
1342
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1310
1343
|
const base = parse(childrenArray[0]) || "";
|
|
1311
1344
|
const over = parse(childrenArray[1]) || "";
|
|
1312
|
-
const
|
|
1345
|
+
const overNode = childrenArray[1];
|
|
1346
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1347
|
+
const overText = NodeTool.getNodeText(overNode)?.trim() || "";
|
|
1313
1348
|
const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
|
|
1314
1349
|
|
|
1350
|
+
// Handle arrows with extensible commands
|
|
1351
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow{${over}}`;
|
|
1352
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow{${over}}`;
|
|
1353
|
+
|
|
1315
1354
|
// Handle biology notation (double overline)
|
|
1316
1355
|
if (overText === "¯") {
|
|
1317
1356
|
const parentNode = node.parentNode;
|
|
@@ -1327,6 +1366,27 @@
|
|
|
1327
1366
|
|
|
1328
1367
|
if (overText === "→" && isAccent) return `\\vec{${base}}`;
|
|
1329
1368
|
if (overText === "^" && isAccent) return `\\hat{${base}}`;
|
|
1369
|
+
if (overText === "\u23DE") return `\\overbrace{${base}}`;
|
|
1370
|
+
|
|
1371
|
+
// Check for nested overbrace (layer-2)
|
|
1372
|
+
if (NodeTool.getNodeName(overNode) === "mover") {
|
|
1373
|
+
const innerChildren = Array.from(NodeTool.getChildren(overNode));
|
|
1374
|
+
if (innerChildren.length >= 2) {
|
|
1375
|
+
const innerBaseText = NodeTool.getNodeText(innerChildren[0]).trim();
|
|
1376
|
+
if (innerBaseText === "\u23DE") {
|
|
1377
|
+
const label = parse(innerChildren[1]);
|
|
1378
|
+
return `\\overbrace{${base}}\\limits^{${label}}`;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (base.startsWith("\\overbrace") || base.startsWith("\\underbrace")) {
|
|
1384
|
+
return `${base}\\limits^{${over}}`;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (isLimitOperator(base)) {
|
|
1388
|
+
return `${base}\\limits^{${over}}`;
|
|
1389
|
+
}
|
|
1330
1390
|
return `\\overset{${over}}{${base}}`;
|
|
1331
1391
|
};
|
|
1332
1392
|
break;
|
|
@@ -1337,10 +1397,21 @@
|
|
|
1337
1397
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1338
1398
|
const base = parse(childrenArray[0]) || "";
|
|
1339
1399
|
const under = parse(childrenArray[1]) || "";
|
|
1400
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1401
|
+
const underText = NodeTool.getNodeText(childrenArray[1])?.trim() || "";
|
|
1340
1402
|
const isUnderAccent =
|
|
1341
1403
|
NodeTool.getAttr(node, "accentunder", "false") === "true";
|
|
1342
1404
|
|
|
1405
|
+
// Handle arrows with extensible commands
|
|
1406
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{}`;
|
|
1407
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{}`;
|
|
1408
|
+
|
|
1343
1409
|
if (base === "∫") return `\\int_{${under}}`;
|
|
1410
|
+
if (underText === "\u23DF") return `\\underbrace{${base}}`;
|
|
1411
|
+
|
|
1412
|
+
if (isLimitOperator(base)) {
|
|
1413
|
+
return `${base}\\limits_{${under}}`;
|
|
1414
|
+
}
|
|
1344
1415
|
return `\\underset{${under}}{${base}}`;
|
|
1345
1416
|
};
|
|
1346
1417
|
break;
|
|
@@ -1354,19 +1425,19 @@
|
|
|
1354
1425
|
const over = parse(childrenArray[2]);
|
|
1355
1426
|
const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
|
|
1356
1427
|
|
|
1357
|
-
// Special handling for chemical reaction arrow
|
|
1358
|
-
if (
|
|
1359
|
-
|
|
1360
|
-
NodeTool.getNodeName(childrenArray[1]) === "msup"
|
|
1361
|
-
) {
|
|
1362
|
-
return `\\xrightarrow[${under}]{${over}}`;
|
|
1363
|
-
}
|
|
1428
|
+
// Special handling for chemical reaction arrow and other arrows
|
|
1429
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{${over}}`;
|
|
1430
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{${over}}`;
|
|
1364
1431
|
|
|
1365
1432
|
if (baseText === "∫") return `\\int_{${under}}^{${over}}`;
|
|
1366
1433
|
if (baseText === "∑") return `\\sum_{${under}}^{${over}}`;
|
|
1367
1434
|
if (baseText === "∏") return `\\prod_{${under}}^{${over}}`;
|
|
1368
1435
|
if (baseText === "|") return `\\big|_{${under}}^{${over}}`;
|
|
1369
|
-
|
|
1436
|
+
|
|
1437
|
+
if (isLimitOperator(base)) {
|
|
1438
|
+
return `${base}\\limits_{${under}}^{${over}}`;
|
|
1439
|
+
}
|
|
1440
|
+
return `\\underset{${under}}{\\overset{${over}}{${base}}}`;
|
|
1370
1441
|
};
|
|
1371
1442
|
break;
|
|
1372
1443
|
|
|
@@ -1374,30 +1445,56 @@
|
|
|
1374
1445
|
render = function (node, children) {
|
|
1375
1446
|
const childrenArray = Array.from(children);
|
|
1376
1447
|
if (!childrenArray || childrenArray.length < 1) return "";
|
|
1377
|
-
const base = parse(childrenArray[0]);
|
|
1378
|
-
|
|
1379
|
-
|
|
1448
|
+
const base = parse(childrenArray[0]) || "";
|
|
1449
|
+
|
|
1450
|
+
const postSub = [];
|
|
1451
|
+
const postSup = [];
|
|
1452
|
+
const preSub = [];
|
|
1453
|
+
const preSup = [];
|
|
1454
|
+
|
|
1380
1455
|
let i = 1;
|
|
1456
|
+
let inPrescripts = false;
|
|
1381
1457
|
|
|
1382
1458
|
while (i < childrenArray.length) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1459
|
+
const name = NodeTool.getNodeName(childrenArray[i]);
|
|
1460
|
+
if (name === "mprescripts") {
|
|
1461
|
+
inPrescripts = true;
|
|
1462
|
+
i += 1;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const subNode = childrenArray[i];
|
|
1467
|
+
const supNode = childrenArray[i + 1];
|
|
1468
|
+
if (!subNode || !supNode) break;
|
|
1469
|
+
|
|
1470
|
+
const sub = parse(subNode) || "";
|
|
1471
|
+
const sup = parse(supNode) || "";
|
|
1472
|
+
|
|
1473
|
+
if (inPrescripts) {
|
|
1474
|
+
if (sub) preSub.push(sub);
|
|
1475
|
+
if (sup) preSup.push(sup);
|
|
1391
1476
|
} else {
|
|
1392
|
-
if (
|
|
1393
|
-
|
|
1394
|
-
childrenArray[i + 1]
|
|
1395
|
-
)}}`;
|
|
1396
|
-
i += 2;
|
|
1397
|
-
} else break;
|
|
1477
|
+
if (sub) postSub.push(sub);
|
|
1478
|
+
if (sup) postSup.push(sup);
|
|
1398
1479
|
}
|
|
1480
|
+
|
|
1481
|
+
i += 2;
|
|
1399
1482
|
}
|
|
1400
|
-
|
|
1483
|
+
|
|
1484
|
+
const preSubStr = preSub.join(" ");
|
|
1485
|
+
const preSupStr = preSup.join(" ");
|
|
1486
|
+
const postSubStr = postSub.join(" ");
|
|
1487
|
+
const postSupStr = postSup.join(" ");
|
|
1488
|
+
|
|
1489
|
+
let pre = "";
|
|
1490
|
+
if (preSubStr) pre += `_{${preSubStr}}`;
|
|
1491
|
+
if (preSupStr) pre += `^{${preSupStr}}`;
|
|
1492
|
+
|
|
1493
|
+
let post = "";
|
|
1494
|
+
if (postSubStr) post += `_{${postSubStr}}`;
|
|
1495
|
+
if (postSupStr) post += `^{${postSupStr}}`;
|
|
1496
|
+
|
|
1497
|
+
return `${pre}${wrapBaseForScript(base)}${post}`;
|
|
1401
1498
|
};
|
|
1402
1499
|
break;
|
|
1403
1500
|
|
|
@@ -1498,7 +1595,28 @@
|
|
|
1498
1595
|
const num = parse(childrenArray[0]);
|
|
1499
1596
|
const den = parse(childrenArray[1]);
|
|
1500
1597
|
const linethickness = NodeTool.getAttr(node, "linethickness", "medium");
|
|
1501
|
-
|
|
1598
|
+
const bevelled = NodeTool.getAttr(node, "bevelled", "false");
|
|
1599
|
+
|
|
1600
|
+
if (bevelled === "true") {
|
|
1601
|
+
return `{}^{${num}}/_{${den}}`;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (["0", "0px"].indexOf(linethickness) > -1) {
|
|
1605
|
+
const prevNode = NodeTool.getPrevNode(node);
|
|
1606
|
+
const nextNode = NodeTool.getNextNode(node);
|
|
1607
|
+
if (
|
|
1608
|
+
prevNode &&
|
|
1609
|
+
NodeTool.getNodeName(prevNode) === "mo" &&
|
|
1610
|
+
NodeTool.getNodeText(prevNode).trim() === "(" &&
|
|
1611
|
+
nextNode &&
|
|
1612
|
+
NodeTool.getNodeName(nextNode) === "mo" &&
|
|
1613
|
+
NodeTool.getNodeText(nextNode).trim() === ")"
|
|
1614
|
+
) {
|
|
1615
|
+
return `\\DELETE_BRACKET_L\\binom{${num}}{${den}}\\DELETE_BRACKET_R`;
|
|
1616
|
+
}
|
|
1617
|
+
return `{}_{${den}}^{${num}}`;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1502
1620
|
return `\\frac{${num}}{${den}}`;
|
|
1503
1621
|
};
|
|
1504
1622
|
break;
|
|
@@ -1508,7 +1626,10 @@
|
|
|
1508
1626
|
const childrenArray = Array.from(children);
|
|
1509
1627
|
const open = NodeTool.getAttr(node, "open", "(");
|
|
1510
1628
|
const close = NodeTool.getAttr(node, "close", ")");
|
|
1511
|
-
const
|
|
1629
|
+
const separatorsStr = NodeTool.getAttr(node, "separators", ",");
|
|
1630
|
+
const separators = separatorsStr
|
|
1631
|
+
.split("")
|
|
1632
|
+
.filter((c) => c.trim().length === 1);
|
|
1512
1633
|
|
|
1513
1634
|
// Xử lý đặc biệt cho trường hợp dấu ngoặc đơn |
|
|
1514
1635
|
if (open === "|") {
|
|
@@ -1559,9 +1680,10 @@
|
|
|
1559
1680
|
parts.push(parse(child));
|
|
1560
1681
|
if (
|
|
1561
1682
|
index < childrenArray.length - 1 &&
|
|
1562
|
-
separators
|
|
1683
|
+
separators.length > 0
|
|
1563
1684
|
) {
|
|
1564
|
-
|
|
1685
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1686
|
+
if (sep) parts.push(sep);
|
|
1565
1687
|
}
|
|
1566
1688
|
});
|
|
1567
1689
|
return `\\left[${parts.join("")}\\right)`;
|
|
@@ -1573,18 +1695,20 @@
|
|
|
1573
1695
|
parts.push(parse(child));
|
|
1574
1696
|
if (
|
|
1575
1697
|
index < childrenArray.length - 1 &&
|
|
1576
|
-
separators
|
|
1698
|
+
separators.length > 0
|
|
1577
1699
|
) {
|
|
1578
|
-
|
|
1700
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1701
|
+
if (sep) parts.push(sep);
|
|
1579
1702
|
}
|
|
1580
1703
|
});
|
|
1581
1704
|
const content = parts.join("");
|
|
1582
1705
|
|
|
1583
1706
|
if (open === "{" && close === "}") return `\\{${content}\\}`;
|
|
1584
1707
|
if (open === "|" && close === "|") return `\\left|${content}\\right|`;
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
return
|
|
1708
|
+
const left = open ? Brackets.parseLeft(open) : "";
|
|
1709
|
+
const right = close ? Brackets.parseRight(close) : "";
|
|
1710
|
+
if (!close && open) return `${left}${content}\\right.`;
|
|
1711
|
+
return `${left}${content}${right}`;
|
|
1588
1712
|
};
|
|
1589
1713
|
break;
|
|
1590
1714
|
|
|
@@ -1610,10 +1734,17 @@
|
|
|
1610
1734
|
case "mn":
|
|
1611
1735
|
case "mo":
|
|
1612
1736
|
case "ms":
|
|
1613
|
-
case "mtext":
|
|
1614
1737
|
render = getRender_joinSeparator("@content");
|
|
1615
1738
|
break;
|
|
1616
1739
|
|
|
1740
|
+
case "mtext":
|
|
1741
|
+
render = function (node, children) {
|
|
1742
|
+
const childrenArray = Array.from(children);
|
|
1743
|
+
const content = renderChildren(childrenArray).join("");
|
|
1744
|
+
return `\\text{${content}}`;
|
|
1745
|
+
};
|
|
1746
|
+
break;
|
|
1747
|
+
|
|
1617
1748
|
case "mphantom":
|
|
1618
1749
|
render = function (node, children) {
|
|
1619
1750
|
const childrenArray = Array.from(children);
|