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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
}(this, (function () { 'use strict';
|
|
6
6
|
|
|
7
7
|
const Brackets = {
|
|
8
|
-
left: ['(', '[', '{', '|', '
|
|
9
|
-
right: [')', ']', '}', '|', '
|
|
8
|
+
left: ['(', '[', '{', '|', '\u2016', '\u27E8', '\u230A', '\u2308', '\u231C'],
|
|
9
|
+
right: [')', ']', '}', '|', '\u2016', '\u27E9', '\u230B', '\u2309', '\u231D'],
|
|
10
10
|
isPair: function(l, r){
|
|
11
11
|
const idx = this.left.indexOf(l);
|
|
12
12
|
return r === this.right[idx];
|
|
@@ -28,17 +28,17 @@
|
|
|
28
28
|
case '[':
|
|
29
29
|
case '|': r = `\\left${it}`;
|
|
30
30
|
break;
|
|
31
|
-
case '
|
|
31
|
+
case '\u2016': r = '\\left\\|';
|
|
32
32
|
break;
|
|
33
33
|
case '{': r = '\\left\\{';
|
|
34
34
|
break;
|
|
35
|
-
case '
|
|
35
|
+
case '\u27E8': r = '\\left\\langle ';
|
|
36
36
|
break;
|
|
37
|
-
case '
|
|
37
|
+
case '\u230A': r = '\\left\\lfloor ';
|
|
38
38
|
break;
|
|
39
|
-
case '
|
|
39
|
+
case '\u2308': r = '\\left\\lceil ';
|
|
40
40
|
break;
|
|
41
|
-
case '
|
|
41
|
+
case '\u231C': r = '\\left\\ulcorner ';
|
|
42
42
|
break;
|
|
43
43
|
}
|
|
44
44
|
return (stretchy ? r : r.replace('\\left', ''));
|
|
@@ -52,23 +52,36 @@
|
|
|
52
52
|
case ']':
|
|
53
53
|
case '|': r = `\\right${it}`;
|
|
54
54
|
break;
|
|
55
|
-
case '
|
|
55
|
+
case '\u2016': r = '\\right\\|';
|
|
56
56
|
break;
|
|
57
57
|
case '}': r = '\\right\\}';
|
|
58
58
|
break;
|
|
59
|
-
case '
|
|
59
|
+
case '\u27E9': r = ' \\right\\rangle';
|
|
60
60
|
break;
|
|
61
|
-
case '
|
|
61
|
+
case '\u230B': r = ' \\right\\rfloor';
|
|
62
62
|
break;
|
|
63
|
-
case '
|
|
63
|
+
case '\u2309': r = ' \\right\\rceil';
|
|
64
64
|
break;
|
|
65
|
-
case '
|
|
65
|
+
case '\u231D': r = ' \\right\\urcorner';
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
68
|
return (stretchy ? r : r.replace('\\right', ''));
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
var _nodeResolve_empty = {};
|
|
73
|
+
|
|
74
|
+
var _nodeResolve_empty$1 = /*#__PURE__*/Object.freeze({
|
|
75
|
+
__proto__: null,
|
|
76
|
+
'default': _nodeResolve_empty
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function getCjsExportFromNamespace (n) {
|
|
80
|
+
return n && n['default'] || n;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
var require$$0 = getCjsExportFromNamespace(_nodeResolve_empty$1);
|
|
84
|
+
|
|
72
85
|
/*
|
|
73
86
|
* Set up window for Node.js
|
|
74
87
|
*/
|
|
@@ -98,11 +111,17 @@
|
|
|
98
111
|
function createHTMLParser () {
|
|
99
112
|
const Parser = function () {};
|
|
100
113
|
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
const hasDocument =
|
|
115
|
+
typeof document !== 'undefined' &&
|
|
116
|
+
document &&
|
|
117
|
+
document.implementation &&
|
|
118
|
+
typeof document.implementation.createHTMLDocument === 'function';
|
|
119
|
+
|
|
120
|
+
if (hasDocument) {
|
|
121
|
+
if (typeof process !== 'undefined' && true && shouldUseActiveX()) {
|
|
103
122
|
Parser.prototype.parseFromString = function (string) {
|
|
104
123
|
const doc = new window.ActiveXObject('htmlfile');
|
|
105
|
-
doc.designMode = 'on';
|
|
124
|
+
doc.designMode = 'on';
|
|
106
125
|
doc.open();
|
|
107
126
|
doc.write(string);
|
|
108
127
|
doc.close();
|
|
@@ -119,11 +138,9 @@
|
|
|
119
138
|
}
|
|
120
139
|
} else {
|
|
121
140
|
Parser.prototype.parseFromString = function (string) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
doc.close();
|
|
126
|
-
return doc
|
|
141
|
+
const domino = require$$0;
|
|
142
|
+
const window = domino.createWindow(string || '');
|
|
143
|
+
return window.document
|
|
127
144
|
};
|
|
128
145
|
}
|
|
129
146
|
return Parser
|
|
@@ -141,11 +158,54 @@
|
|
|
141
158
|
|
|
142
159
|
const HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
|
|
143
160
|
|
|
161
|
+
function normalizeMathMLInput(input) {
|
|
162
|
+
let s = input == null ? '' : String(input);
|
|
163
|
+
|
|
164
|
+
if (s.includes('<math') && /\\[ntr"]/.test(s)) {
|
|
165
|
+
s = s
|
|
166
|
+
.replace(/\\r\\n/g, '\n')
|
|
167
|
+
.replace(/\\n/g, '\n')
|
|
168
|
+
.replace(/\\t/g, '\t')
|
|
169
|
+
.replace(/\\"/g, '"')
|
|
170
|
+
.replace(/\\'/g, "'");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
s = s.replace(/^\s*<\?xml[\s\S]*?\?>\s*/i, '');
|
|
174
|
+
|
|
175
|
+
s = s.replace(
|
|
176
|
+
/xmlns\s*=\s*(["'])\s*`?\s*(https?:\/\/www\.w3\.org\/1998\/Math\/MathML)\s*`?\s*\1/gi,
|
|
177
|
+
'xmlns="$2"'
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
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"');
|
|
181
|
+
|
|
182
|
+
if (!/<math\b/i.test(s) && /<(msub|mrow|mi|mn|mo|mfrac|msup|msubsup|munder|mover|munderover|mtable)\b/i.test(s)) {
|
|
183
|
+
s = `<math xmlns="http://www.w3.org/1998/Math/MathML">${s}</math>`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return s;
|
|
187
|
+
}
|
|
188
|
+
|
|
144
189
|
const NodeTool = {
|
|
145
190
|
parseMath: function(html) {
|
|
191
|
+
const normalized = normalizeMathMLInput(html);
|
|
146
192
|
const parser = new HTMLParser();
|
|
147
|
-
const doc = parser.parseFromString(
|
|
148
|
-
|
|
193
|
+
const doc = parser.parseFromString(normalized, 'text/html');
|
|
194
|
+
let math = doc && doc.querySelector ? doc.querySelector('math') : null;
|
|
195
|
+
|
|
196
|
+
if (!math) {
|
|
197
|
+
const match = normalized.match(/<math\b[\s\S]*?<\/math>/i);
|
|
198
|
+
if (match) {
|
|
199
|
+
const retryDoc = parser.parseFromString(match[0], 'text/html');
|
|
200
|
+
math = retryDoc && retryDoc.querySelector ? retryDoc.querySelector('math') : null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!math) {
|
|
205
|
+
throw new Error('Invalid MathML: missing <math> root element');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return math;
|
|
149
209
|
},
|
|
150
210
|
getChildren: function(node) {
|
|
151
211
|
return node.children;
|
|
@@ -281,24 +341,24 @@
|
|
|
281
341
|
bigCommand: {
|
|
282
342
|
decimals: [8721, 8719, 8720, 10753, 10754, 10752, 8899, 8898, 10756, 10758, 8897, 8896, 8747, 8750, 8748, 8749, 10764, 8747],
|
|
283
343
|
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",
|
|
344
|
+
"\\sum ",
|
|
345
|
+
"\\prod ",
|
|
346
|
+
"\\coprod ",
|
|
347
|
+
"\\bigoplus ",
|
|
348
|
+
"\\bigotimes ",
|
|
349
|
+
"\\bigodot ",
|
|
350
|
+
"\\bigcup ",
|
|
351
|
+
"\\bigcap ",
|
|
352
|
+
"\\biguplus ",
|
|
353
|
+
"\\bigsqcup ",
|
|
354
|
+
"\\bigvee ",
|
|
355
|
+
"\\bigwedge ",
|
|
356
|
+
"\\int ",
|
|
357
|
+
"\\oint ",
|
|
358
|
+
"\\iint ",
|
|
359
|
+
"\\iiint ",
|
|
360
|
+
"\\iiiint ",
|
|
361
|
+
"\\idotsint ",
|
|
302
362
|
]
|
|
303
363
|
},
|
|
304
364
|
|
|
@@ -616,9 +676,10 @@
|
|
|
616
676
|
// Thêm xử lý cho các thẻ MathML khác
|
|
617
677
|
result = result
|
|
618
678
|
.replace(/∞/g, "\\infty") // Vô cực
|
|
619
|
-
.replace(
|
|
620
|
-
|
|
621
|
-
|
|
679
|
+
.replace(/∫/g, " \\int "); // Tích phân
|
|
680
|
+
|
|
681
|
+
// Đả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)
|
|
682
|
+
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim(?!its)|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
622
683
|
|
|
623
684
|
return result;
|
|
624
685
|
}
|
|
@@ -764,20 +825,24 @@
|
|
|
764
825
|
"±": " \\pm ",
|
|
765
826
|
"×": " \\times ",
|
|
766
827
|
"÷": " \\div ",
|
|
767
|
-
"∑": "
|
|
768
|
-
"∏": "
|
|
769
|
-
"∫": "\\int",
|
|
828
|
+
"∑": "\\sum ",
|
|
829
|
+
"∏": "\\prod ",
|
|
830
|
+
"∫": "\\int ",
|
|
770
831
|
"−": "-",
|
|
771
832
|
"≠": " \\neq ",
|
|
772
833
|
">": " > ",
|
|
773
834
|
"=": " = ",
|
|
835
|
+
"(": "(",
|
|
836
|
+
")": ")",
|
|
774
837
|
",": ", ", // Dấu phẩy trong tập hợp
|
|
775
838
|
";": ";",
|
|
776
839
|
Ω: "\\Omega",
|
|
777
840
|
"|": " \\mid ", // PATCH: set-builder mid
|
|
778
841
|
π: " \\pi ", // PATCH: Greek letter
|
|
842
|
+
"...": "\\dots",
|
|
779
843
|
};
|
|
780
|
-
|
|
844
|
+
const res = operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
|
|
845
|
+
return res;
|
|
781
846
|
}
|
|
782
847
|
// --- END PATCH ---
|
|
783
848
|
|
|
@@ -804,6 +869,20 @@
|
|
|
804
869
|
// Math Number
|
|
805
870
|
function parseElementMn(node) {
|
|
806
871
|
let it = NodeTool.getNodeText(node).trim();
|
|
872
|
+
// Loại bỏ các ký tự điều khiển hoặc khoảng trắng lạ
|
|
873
|
+
it = it.replace(/[\u0000-\u001F\u007F-\u009F\u00A0]/g, "");
|
|
874
|
+
|
|
875
|
+
// Danh sách các hàm toán học mở rộng
|
|
876
|
+
const mathFunctions = ["cos", "sin", "tan", "cot", "arccos", "arcsin", "arctan", "arccot", "log", "ln", "lim", "sinh", "cosh", "tanh", "sec", "csc"];
|
|
877
|
+
|
|
878
|
+
if (mathFunctions.some(fn => it.toLowerCase().includes(fn))) {
|
|
879
|
+
// Tìm hàm khớp chính xác nhất
|
|
880
|
+
for (const fn of mathFunctions) {
|
|
881
|
+
if (it.toLowerCase() === fn) {
|
|
882
|
+
return "\\" + fn + " ";
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
807
886
|
return escapeSpecialChars(it);
|
|
808
887
|
}
|
|
809
888
|
|
|
@@ -837,8 +916,8 @@
|
|
|
837
916
|
// Nếu đã là lệnh LaTeX thì giữ nguyên
|
|
838
917
|
if (content.startsWith("\\")) return content;
|
|
839
918
|
|
|
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)) {
|
|
919
|
+
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH, ..., k times)
|
|
920
|
+
if (/^[A-Za-z][A-Za-z0-9\s]*$/.test(content)) {
|
|
842
921
|
return `\\text{${content}}`;
|
|
843
922
|
}
|
|
844
923
|
|
|
@@ -893,6 +972,22 @@
|
|
|
893
972
|
return text;
|
|
894
973
|
}
|
|
895
974
|
|
|
975
|
+
function wrapBaseForScript(base) {
|
|
976
|
+
const t = (base ?? "").trim();
|
|
977
|
+
if (!t) return "";
|
|
978
|
+
if (t.startsWith("{") && t.endsWith("}")) return t;
|
|
979
|
+
if (/^\\[a-zA-Z]+$/.test(t)) return t;
|
|
980
|
+
const needsWrap =
|
|
981
|
+
t.includes("_") ||
|
|
982
|
+
t.includes("^") ||
|
|
983
|
+
/\s/.test(t) ||
|
|
984
|
+
t.startsWith("\\left") ||
|
|
985
|
+
t.startsWith("\\right") ||
|
|
986
|
+
/\\[a-zA-Z]+/.test(t) ||
|
|
987
|
+
/[{}]/.test(t);
|
|
988
|
+
return needsWrap ? `{${t}}` : t;
|
|
989
|
+
}
|
|
990
|
+
|
|
896
991
|
function parseContainer(node, children) {
|
|
897
992
|
const render = getRender(node);
|
|
898
993
|
if (render) {
|
|
@@ -911,22 +1006,6 @@
|
|
|
911
1006
|
// lefts là mảng object: { op: "(", index: 5 }
|
|
912
1007
|
let lefts = [];
|
|
913
1008
|
|
|
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
1009
|
Array.prototype.forEach.call(children, (node, idx) => {
|
|
931
1010
|
// PATCH: Thin space between variables/numbers in mfrac numerator (k 2 π)
|
|
932
1011
|
if (
|
|
@@ -954,55 +1033,59 @@
|
|
|
954
1033
|
if (Brackets.contains(op)) {
|
|
955
1034
|
let stretchy = NodeTool.getAttr(node, "stretchy", "true");
|
|
956
1035
|
stretchy = ["", "true"].indexOf(stretchy) > -1;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
|
|
1036
|
+
const parentNode = node.parentNode;
|
|
1037
|
+
const isInPower = parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
1038
|
+
if (isInPower) {
|
|
1039
|
+
stretchy = false;
|
|
961
1040
|
}
|
|
962
1041
|
|
|
963
1042
|
if (Brackets.isRight(op)) {
|
|
964
1043
|
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.
|
|
1044
|
+
if (nearLeftObj && Brackets.isPair(nearLeftObj.op, op)) {
|
|
1045
|
+
const leftIdx = nearLeftObj.index;
|
|
1046
|
+
const leftRendered =
|
|
1047
|
+
typeof leftIdx === "number" ? String(parts[leftIdx] ?? "") : "";
|
|
1048
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1049
|
+
|
|
1050
|
+
const leftRenderedTrimmed = leftRendered.trim();
|
|
1051
|
+
const rightRenderedTrimmed = String(rightRendered).trim();
|
|
1052
|
+
|
|
1053
|
+
if (
|
|
1054
|
+
leftRenderedTrimmed.startsWith("\\left") &&
|
|
1055
|
+
!rightRenderedTrimmed.startsWith("\\right")
|
|
1056
|
+
) {
|
|
1057
|
+
parts[leftIdx] = leftRendered.replace("\\left", "");
|
|
1058
|
+
} else if (
|
|
1059
|
+
!leftRenderedTrimmed.startsWith("\\left") &&
|
|
1060
|
+
rightRenderedTrimmed.startsWith("\\right")
|
|
1061
|
+
) {
|
|
1062
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
979
1063
|
}
|
|
980
|
-
|
|
1064
|
+
|
|
1065
|
+
if (rightRendered) parts.push(rightRendered);
|
|
981
1066
|
lefts.pop();
|
|
1067
|
+
} else if (Brackets.isLeft(op)) {
|
|
1068
|
+
// If it's a Right bracket but doesn't match the current Left,
|
|
1069
|
+
// AND it is also a Left bracket (e.g. '|'), treat it as a new Left.
|
|
1070
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1071
|
+
if (partToPush) parts.push(partToPush);
|
|
1072
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
982
1073
|
} else {
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1074
|
+
// Unmatched right bracket
|
|
1075
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1076
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
1077
|
+
if (rightRendered) parts.push(rightRendered);
|
|
987
1078
|
}
|
|
988
1079
|
} else {
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
let partToPush = "";
|
|
995
|
-
if (stretchy && !isInPower) {
|
|
996
|
-
partToPush = `\\left${escapedOp}`;
|
|
1080
|
+
// Must be Left bracket (or only Left)
|
|
1081
|
+
if (Brackets.isLeft(op)) {
|
|
1082
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1083
|
+
if (partToPush) parts.push(partToPush);
|
|
1084
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
997
1085
|
} else {
|
|
998
|
-
|
|
1086
|
+
// Should not happen if Brackets.contains is correct
|
|
1087
|
+
if (op) parts.push(op);
|
|
999
1088
|
}
|
|
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
1089
|
}
|
|
1007
1090
|
} else {
|
|
1008
1091
|
const parsedOperator = parseOperator(node);
|
|
@@ -1011,45 +1094,6 @@
|
|
|
1011
1094
|
parts.push(parsedOperator);
|
|
1012
1095
|
}
|
|
1013
1096
|
}
|
|
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
1097
|
} else {
|
|
1054
1098
|
// Các node khác như <mtext>, #text, v.v.
|
|
1055
1099
|
const parsed = parse(node);
|
|
@@ -1061,40 +1105,13 @@
|
|
|
1061
1105
|
});
|
|
1062
1106
|
|
|
1063
1107
|
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1064
|
-
//
|
|
1108
|
+
// để tránh sinh LaTeX không hợp lệ (\left mà không có \right)
|
|
1065
1109
|
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
1110
|
lefts.forEach((leftObj) => {
|
|
1082
1111
|
const idx = leftObj && leftObj.index;
|
|
1083
1112
|
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
|
|
1113
|
+
// Use regex with whitespace support and global flag just in case
|
|
1114
|
+
parts[idx] = String(parts[idx]).replace(/\\left/g, "");
|
|
1098
1115
|
});
|
|
1099
1116
|
}
|
|
1100
1117
|
|
|
@@ -1102,6 +1119,44 @@
|
|
|
1102
1119
|
return parts;
|
|
1103
1120
|
}
|
|
1104
1121
|
|
|
1122
|
+
function isLimitOperator(base) {
|
|
1123
|
+
const t = (base || "").trim();
|
|
1124
|
+
const ops = [
|
|
1125
|
+
"\\sum",
|
|
1126
|
+
"\\prod",
|
|
1127
|
+
"\\coprod",
|
|
1128
|
+
"\\int",
|
|
1129
|
+
"\\oint",
|
|
1130
|
+
"\\bigcap",
|
|
1131
|
+
"\\bigcup",
|
|
1132
|
+
"\\bigsqcup",
|
|
1133
|
+
"\\bigvee",
|
|
1134
|
+
"\\bigwedge",
|
|
1135
|
+
"\\bigodot",
|
|
1136
|
+
"\\bigotimes",
|
|
1137
|
+
"\\bigoplus",
|
|
1138
|
+
"\\biguplus",
|
|
1139
|
+
"\\lim",
|
|
1140
|
+
"\\max",
|
|
1141
|
+
"\\min",
|
|
1142
|
+
"\\sup",
|
|
1143
|
+
"\\inf",
|
|
1144
|
+
"\\det",
|
|
1145
|
+
"\\gcd",
|
|
1146
|
+
"\\Pr",
|
|
1147
|
+
"\\limsup",
|
|
1148
|
+
"\\liminf",
|
|
1149
|
+
];
|
|
1150
|
+
return ops.some(
|
|
1151
|
+
(op) =>
|
|
1152
|
+
t === op ||
|
|
1153
|
+
t.startsWith(op + " ") ||
|
|
1154
|
+
t.startsWith(op + "\\") ||
|
|
1155
|
+
t.startsWith(op + "^") ||
|
|
1156
|
+
t.startsWith(op + "_")
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1105
1160
|
function getRender(node) {
|
|
1106
1161
|
let render = undefined;
|
|
1107
1162
|
const nodeName = NodeTool.getNodeName(node);
|
|
@@ -1250,9 +1305,11 @@
|
|
|
1250
1305
|
NodeTool.getNodeText(sub.firstChild).trim() === "")
|
|
1251
1306
|
) {
|
|
1252
1307
|
const lastChild = sub.lastChild;
|
|
1253
|
-
return lastChild
|
|
1308
|
+
return lastChild
|
|
1309
|
+
? `${wrapBaseForScript(base)}_{${parse(lastChild)}}`
|
|
1310
|
+
: base;
|
|
1254
1311
|
}
|
|
1255
|
-
return `${base}_{${parse(sub)}}`;
|
|
1312
|
+
return `${wrapBaseForScript(base)}_{${parse(sub)}}`;
|
|
1256
1313
|
};
|
|
1257
1314
|
break;
|
|
1258
1315
|
|
|
@@ -1260,22 +1317,9 @@
|
|
|
1260
1317
|
render = function (node, children) {
|
|
1261
1318
|
const childrenArray = Array.from(children);
|
|
1262
1319
|
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
|
-
}
|
|
1320
|
+
const base = parse(childrenArray[0]) || "";
|
|
1277
1321
|
const sup = parse(childrenArray[1]) || "";
|
|
1278
|
-
return `${base}^{${sup}}`;
|
|
1322
|
+
return `${wrapBaseForScript(base)}^{${sup}}`;
|
|
1279
1323
|
};
|
|
1280
1324
|
break;
|
|
1281
1325
|
|
|
@@ -1299,7 +1343,7 @@
|
|
|
1299
1343
|
.join("");
|
|
1300
1344
|
return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
|
|
1301
1345
|
}
|
|
1302
|
-
return `${base}_{${sub}}^{${sup}}`;
|
|
1346
|
+
return `${wrapBaseForScript(base)}_{${sub}}^{${sup}}`;
|
|
1303
1347
|
};
|
|
1304
1348
|
break;
|
|
1305
1349
|
|
|
@@ -1309,9 +1353,15 @@
|
|
|
1309
1353
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1310
1354
|
const base = parse(childrenArray[0]) || "";
|
|
1311
1355
|
const over = parse(childrenArray[1]) || "";
|
|
1312
|
-
const
|
|
1356
|
+
const overNode = childrenArray[1];
|
|
1357
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1358
|
+
const overText = NodeTool.getNodeText(overNode)?.trim() || "";
|
|
1313
1359
|
const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
|
|
1314
1360
|
|
|
1361
|
+
// Handle arrows with extensible commands
|
|
1362
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow{${over}}`;
|
|
1363
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow{${over}}`;
|
|
1364
|
+
|
|
1315
1365
|
// Handle biology notation (double overline)
|
|
1316
1366
|
if (overText === "¯") {
|
|
1317
1367
|
const parentNode = node.parentNode;
|
|
@@ -1327,6 +1377,27 @@
|
|
|
1327
1377
|
|
|
1328
1378
|
if (overText === "→" && isAccent) return `\\vec{${base}}`;
|
|
1329
1379
|
if (overText === "^" && isAccent) return `\\hat{${base}}`;
|
|
1380
|
+
if (overText === "\u23DE") return `\\overbrace{${base}}`;
|
|
1381
|
+
|
|
1382
|
+
// Check for nested overbrace (layer-2)
|
|
1383
|
+
if (NodeTool.getNodeName(overNode) === "mover") {
|
|
1384
|
+
const innerChildren = Array.from(NodeTool.getChildren(overNode));
|
|
1385
|
+
if (innerChildren.length >= 2) {
|
|
1386
|
+
const innerBaseText = NodeTool.getNodeText(innerChildren[0]).trim();
|
|
1387
|
+
if (innerBaseText === "\u23DE") {
|
|
1388
|
+
const label = parse(innerChildren[1]);
|
|
1389
|
+
return `\\overbrace{${base}}\\limits^{${label}}`;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (base.startsWith("\\overbrace") || base.startsWith("\\underbrace")) {
|
|
1395
|
+
return `${base}\\limits^{${over}}`;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (isLimitOperator(base)) {
|
|
1399
|
+
return `${base}\\limits^{${over}}`;
|
|
1400
|
+
}
|
|
1330
1401
|
return `\\overset{${over}}{${base}}`;
|
|
1331
1402
|
};
|
|
1332
1403
|
break;
|
|
@@ -1337,10 +1408,21 @@
|
|
|
1337
1408
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1338
1409
|
const base = parse(childrenArray[0]) || "";
|
|
1339
1410
|
const under = parse(childrenArray[1]) || "";
|
|
1411
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1412
|
+
const underText = NodeTool.getNodeText(childrenArray[1])?.trim() || "";
|
|
1340
1413
|
const isUnderAccent =
|
|
1341
1414
|
NodeTool.getAttr(node, "accentunder", "false") === "true";
|
|
1342
1415
|
|
|
1416
|
+
// Handle arrows with extensible commands
|
|
1417
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{}`;
|
|
1418
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{}`;
|
|
1419
|
+
|
|
1343
1420
|
if (base === "∫") return `\\int_{${under}}`;
|
|
1421
|
+
if (underText === "\u23DF") return `\\underbrace{${base}}`;
|
|
1422
|
+
|
|
1423
|
+
if (isLimitOperator(base)) {
|
|
1424
|
+
return `${base}\\limits_{${under}}`;
|
|
1425
|
+
}
|
|
1344
1426
|
return `\\underset{${under}}{${base}}`;
|
|
1345
1427
|
};
|
|
1346
1428
|
break;
|
|
@@ -1354,19 +1436,19 @@
|
|
|
1354
1436
|
const over = parse(childrenArray[2]);
|
|
1355
1437
|
const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
|
|
1356
1438
|
|
|
1357
|
-
// Special handling for chemical reaction arrow
|
|
1358
|
-
if (
|
|
1359
|
-
|
|
1360
|
-
NodeTool.getNodeName(childrenArray[1]) === "msup"
|
|
1361
|
-
) {
|
|
1362
|
-
return `\\xrightarrow[${under}]{${over}}`;
|
|
1363
|
-
}
|
|
1439
|
+
// Special handling for chemical reaction arrow and other arrows
|
|
1440
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{${over}}`;
|
|
1441
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{${over}}`;
|
|
1364
1442
|
|
|
1365
1443
|
if (baseText === "∫") return `\\int_{${under}}^{${over}}`;
|
|
1366
1444
|
if (baseText === "∑") return `\\sum_{${under}}^{${over}}`;
|
|
1367
1445
|
if (baseText === "∏") return `\\prod_{${under}}^{${over}}`;
|
|
1368
1446
|
if (baseText === "|") return `\\big|_{${under}}^{${over}}`;
|
|
1369
|
-
|
|
1447
|
+
|
|
1448
|
+
if (isLimitOperator(base)) {
|
|
1449
|
+
return `${base}\\limits_{${under}}^{${over}}`;
|
|
1450
|
+
}
|
|
1451
|
+
return `\\underset{${under}}{\\overset{${over}}{${base}}}`;
|
|
1370
1452
|
};
|
|
1371
1453
|
break;
|
|
1372
1454
|
|
|
@@ -1374,30 +1456,56 @@
|
|
|
1374
1456
|
render = function (node, children) {
|
|
1375
1457
|
const childrenArray = Array.from(children);
|
|
1376
1458
|
if (!childrenArray || childrenArray.length < 1) return "";
|
|
1377
|
-
const base = parse(childrenArray[0]);
|
|
1378
|
-
|
|
1379
|
-
|
|
1459
|
+
const base = parse(childrenArray[0]) || "";
|
|
1460
|
+
|
|
1461
|
+
const postSub = [];
|
|
1462
|
+
const postSup = [];
|
|
1463
|
+
const preSub = [];
|
|
1464
|
+
const preSup = [];
|
|
1465
|
+
|
|
1380
1466
|
let i = 1;
|
|
1467
|
+
let inPrescripts = false;
|
|
1381
1468
|
|
|
1382
1469
|
while (i < childrenArray.length) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1470
|
+
const name = NodeTool.getNodeName(childrenArray[i]);
|
|
1471
|
+
if (name === "mprescripts") {
|
|
1472
|
+
inPrescripts = true;
|
|
1473
|
+
i += 1;
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
const subNode = childrenArray[i];
|
|
1478
|
+
const supNode = childrenArray[i + 1];
|
|
1479
|
+
if (!subNode || !supNode) break;
|
|
1480
|
+
|
|
1481
|
+
const sub = parse(subNode) || "";
|
|
1482
|
+
const sup = parse(supNode) || "";
|
|
1483
|
+
|
|
1484
|
+
if (inPrescripts) {
|
|
1485
|
+
if (sub) preSub.push(sub);
|
|
1486
|
+
if (sup) preSup.push(sup);
|
|
1391
1487
|
} else {
|
|
1392
|
-
if (
|
|
1393
|
-
|
|
1394
|
-
childrenArray[i + 1]
|
|
1395
|
-
)}}`;
|
|
1396
|
-
i += 2;
|
|
1397
|
-
} else break;
|
|
1488
|
+
if (sub) postSub.push(sub);
|
|
1489
|
+
if (sup) postSup.push(sup);
|
|
1398
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
i += 2;
|
|
1399
1493
|
}
|
|
1400
|
-
|
|
1494
|
+
|
|
1495
|
+
const preSubStr = preSub.join(" ");
|
|
1496
|
+
const preSupStr = preSup.join(" ");
|
|
1497
|
+
const postSubStr = postSub.join(" ");
|
|
1498
|
+
const postSupStr = postSup.join(" ");
|
|
1499
|
+
|
|
1500
|
+
let pre = "";
|
|
1501
|
+
if (preSubStr) pre += `_{${preSubStr}}`;
|
|
1502
|
+
if (preSupStr) pre += `^{${preSupStr}}`;
|
|
1503
|
+
|
|
1504
|
+
let post = "";
|
|
1505
|
+
if (postSubStr) post += `_{${postSubStr}}`;
|
|
1506
|
+
if (postSupStr) post += `^{${postSupStr}}`;
|
|
1507
|
+
|
|
1508
|
+
return `${pre}${wrapBaseForScript(base)}${post}`;
|
|
1401
1509
|
};
|
|
1402
1510
|
break;
|
|
1403
1511
|
|
|
@@ -1498,7 +1606,28 @@
|
|
|
1498
1606
|
const num = parse(childrenArray[0]);
|
|
1499
1607
|
const den = parse(childrenArray[1]);
|
|
1500
1608
|
const linethickness = NodeTool.getAttr(node, "linethickness", "medium");
|
|
1501
|
-
|
|
1609
|
+
const bevelled = NodeTool.getAttr(node, "bevelled", "false");
|
|
1610
|
+
|
|
1611
|
+
if (bevelled === "true") {
|
|
1612
|
+
return `{}^{${num}}/_{${den}}`;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
if (["0", "0px"].indexOf(linethickness) > -1) {
|
|
1616
|
+
const prevNode = NodeTool.getPrevNode(node);
|
|
1617
|
+
const nextNode = NodeTool.getNextNode(node);
|
|
1618
|
+
if (
|
|
1619
|
+
prevNode &&
|
|
1620
|
+
NodeTool.getNodeName(prevNode) === "mo" &&
|
|
1621
|
+
NodeTool.getNodeText(prevNode).trim() === "(" &&
|
|
1622
|
+
nextNode &&
|
|
1623
|
+
NodeTool.getNodeName(nextNode) === "mo" &&
|
|
1624
|
+
NodeTool.getNodeText(nextNode).trim() === ")"
|
|
1625
|
+
) {
|
|
1626
|
+
return `\\DELETE_BRACKET_L\\binom{${num}}{${den}}\\DELETE_BRACKET_R`;
|
|
1627
|
+
}
|
|
1628
|
+
return `{}_{${den}}^{${num}}`;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1502
1631
|
return `\\frac{${num}}{${den}}`;
|
|
1503
1632
|
};
|
|
1504
1633
|
break;
|
|
@@ -1508,7 +1637,10 @@
|
|
|
1508
1637
|
const childrenArray = Array.from(children);
|
|
1509
1638
|
const open = NodeTool.getAttr(node, "open", "(");
|
|
1510
1639
|
const close = NodeTool.getAttr(node, "close", ")");
|
|
1511
|
-
const
|
|
1640
|
+
const separatorsStr = NodeTool.getAttr(node, "separators", ",");
|
|
1641
|
+
const separators = separatorsStr
|
|
1642
|
+
.split("")
|
|
1643
|
+
.filter((c) => c.trim().length === 1);
|
|
1512
1644
|
|
|
1513
1645
|
// Xử lý đặc biệt cho trường hợp dấu ngoặc đơn |
|
|
1514
1646
|
if (open === "|") {
|
|
@@ -1559,9 +1691,10 @@
|
|
|
1559
1691
|
parts.push(parse(child));
|
|
1560
1692
|
if (
|
|
1561
1693
|
index < childrenArray.length - 1 &&
|
|
1562
|
-
separators
|
|
1694
|
+
separators.length > 0
|
|
1563
1695
|
) {
|
|
1564
|
-
|
|
1696
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1697
|
+
if (sep) parts.push(sep);
|
|
1565
1698
|
}
|
|
1566
1699
|
});
|
|
1567
1700
|
return `\\left[${parts.join("")}\\right)`;
|
|
@@ -1573,18 +1706,20 @@
|
|
|
1573
1706
|
parts.push(parse(child));
|
|
1574
1707
|
if (
|
|
1575
1708
|
index < childrenArray.length - 1 &&
|
|
1576
|
-
separators
|
|
1709
|
+
separators.length > 0
|
|
1577
1710
|
) {
|
|
1578
|
-
|
|
1711
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1712
|
+
if (sep) parts.push(sep);
|
|
1579
1713
|
}
|
|
1580
1714
|
});
|
|
1581
1715
|
const content = parts.join("");
|
|
1582
1716
|
|
|
1583
1717
|
if (open === "{" && close === "}") return `\\{${content}\\}`;
|
|
1584
1718
|
if (open === "|" && close === "|") return `\\left|${content}\\right|`;
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
return
|
|
1719
|
+
const left = open ? Brackets.parseLeft(open) : "";
|
|
1720
|
+
const right = close ? Brackets.parseRight(close) : "";
|
|
1721
|
+
if (!close && open) return `${left}${content}\\right.`;
|
|
1722
|
+
return `${left}${content}${right}`;
|
|
1588
1723
|
};
|
|
1589
1724
|
break;
|
|
1590
1725
|
|
|
@@ -1610,10 +1745,17 @@
|
|
|
1610
1745
|
case "mn":
|
|
1611
1746
|
case "mo":
|
|
1612
1747
|
case "ms":
|
|
1613
|
-
case "mtext":
|
|
1614
1748
|
render = getRender_joinSeparator("@content");
|
|
1615
1749
|
break;
|
|
1616
1750
|
|
|
1751
|
+
case "mtext":
|
|
1752
|
+
render = function (node, children) {
|
|
1753
|
+
const childrenArray = Array.from(children);
|
|
1754
|
+
const content = renderChildren(childrenArray).join("");
|
|
1755
|
+
return `\\text{${content}}`;
|
|
1756
|
+
};
|
|
1757
|
+
break;
|
|
1758
|
+
|
|
1617
1759
|
case "mphantom":
|
|
1618
1760
|
render = function (node, children) {
|
|
1619
1761
|
const childrenArray = Array.from(children);
|