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