ed-mathml2tex 0.2.3 → 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 +332 -206
- package/lib/mathml2latex.browser.es.js +332 -206
- package/lib/mathml2latex.browser.umd.js +332 -206
- package/lib/mathml2latex.cjs.js +323 -206
- package/lib/mathml2latex.es.js +321 -206
- package/lib/mathml2latex.umd.js +325 -210
- 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;
|
|
@@ -619,7 +679,7 @@
|
|
|
619
679
|
.replace(/∫/g, " \\int "); // Tích phân
|
|
620
680
|
|
|
621
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)
|
|
622
|
-
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
682
|
+
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim(?!its)|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
623
683
|
|
|
624
684
|
return result;
|
|
625
685
|
}
|
|
@@ -765,18 +825,21 @@
|
|
|
765
825
|
"±": " \\pm ",
|
|
766
826
|
"×": " \\times ",
|
|
767
827
|
"÷": " \\div ",
|
|
768
|
-
"∑": "
|
|
769
|
-
"∏": "
|
|
770
|
-
"∫": "
|
|
828
|
+
"∑": "\\sum ",
|
|
829
|
+
"∏": "\\prod ",
|
|
830
|
+
"∫": "\\int ",
|
|
771
831
|
"−": "-",
|
|
772
832
|
"≠": " \\neq ",
|
|
773
833
|
">": " > ",
|
|
774
834
|
"=": " = ",
|
|
835
|
+
"(": "(",
|
|
836
|
+
")": ")",
|
|
775
837
|
",": ", ", // Dấu phẩy trong tập hợp
|
|
776
838
|
";": ";",
|
|
777
839
|
Ω: "\\Omega",
|
|
778
840
|
"|": " \\mid ", // PATCH: set-builder mid
|
|
779
841
|
π: " \\pi ", // PATCH: Greek letter
|
|
842
|
+
"...": "\\dots",
|
|
780
843
|
};
|
|
781
844
|
const res = operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
|
|
782
845
|
return res;
|
|
@@ -853,8 +916,8 @@
|
|
|
853
916
|
// Nếu đã là lệnh LaTeX thì giữ nguyên
|
|
854
917
|
if (content.startsWith("\\")) return content;
|
|
855
918
|
|
|
856
|
-
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH,
|
|
857
|
-
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)) {
|
|
858
921
|
return `\\text{${content}}`;
|
|
859
922
|
}
|
|
860
923
|
|
|
@@ -909,6 +972,22 @@
|
|
|
909
972
|
return text;
|
|
910
973
|
}
|
|
911
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
|
+
|
|
912
991
|
function parseContainer(node, children) {
|
|
913
992
|
const render = getRender(node);
|
|
914
993
|
if (render) {
|
|
@@ -927,22 +1006,6 @@
|
|
|
927
1006
|
// lefts là mảng object: { op: "(", index: 5 }
|
|
928
1007
|
let lefts = [];
|
|
929
1008
|
|
|
930
|
-
if (
|
|
931
|
-
children.length >= 3 &&
|
|
932
|
-
NodeTool.getNodeName(children[0]) === "mo" &&
|
|
933
|
-
NodeTool.getNodeText(children[0]).trim() === "{" &&
|
|
934
|
-
NodeTool.getNodeName(children[children.length - 1]) === "mo" &&
|
|
935
|
-
NodeTool.getNodeText(children[children.length - 1]).trim() === "}"
|
|
936
|
-
) {
|
|
937
|
-
// Render inner content
|
|
938
|
-
const innerContent = Array.prototype.slice
|
|
939
|
-
.call(children, 1, -1)
|
|
940
|
-
.map((child) => parse(child))
|
|
941
|
-
.join(""); // Chỉ trả về nếu nội dung không rỗng
|
|
942
|
-
const result = `\\left\\{${innerContent}\\right\\}`;
|
|
943
|
-
return result;
|
|
944
|
-
}
|
|
945
|
-
|
|
946
1009
|
Array.prototype.forEach.call(children, (node, idx) => {
|
|
947
1010
|
// PATCH: Thin space between variables/numbers in mfrac numerator (k 2 π)
|
|
948
1011
|
if (
|
|
@@ -970,55 +1033,59 @@
|
|
|
970
1033
|
if (Brackets.contains(op)) {
|
|
971
1034
|
let stretchy = NodeTool.getAttr(node, "stretchy", "true");
|
|
972
1035
|
stretchy = ["", "true"].indexOf(stretchy) > -1;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
|
|
1036
|
+
const parentNode = node.parentNode;
|
|
1037
|
+
const isInPower = parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
1038
|
+
if (isInPower) {
|
|
1039
|
+
stretchy = false;
|
|
977
1040
|
}
|
|
978
1041
|
|
|
979
1042
|
if (Brackets.isRight(op)) {
|
|
980
1043
|
const nearLeftObj = lefts[lefts.length - 1];
|
|
981
|
-
if (nearLeftObj) {
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
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", "");
|
|
995
1063
|
}
|
|
996
|
-
|
|
1064
|
+
|
|
1065
|
+
if (rightRendered) parts.push(rightRendered);
|
|
997
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 });
|
|
998
1073
|
} else {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1074
|
+
// Unmatched right bracket
|
|
1075
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1076
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
1077
|
+
if (rightRendered) parts.push(rightRendered);
|
|
1003
1078
|
}
|
|
1004
1079
|
} else {
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
let partToPush = "";
|
|
1011
|
-
if (stretchy && !isInPower) {
|
|
1012
|
-
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 });
|
|
1013
1085
|
} else {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (partToPush) {
|
|
1017
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1018
|
-
parts.push(partToPush);
|
|
1086
|
+
// Should not happen if Brackets.contains is correct
|
|
1087
|
+
if (op) parts.push(op);
|
|
1019
1088
|
}
|
|
1020
|
-
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1021
|
-
lefts.push({ op: op, index: parts.length - 1 });
|
|
1022
1089
|
}
|
|
1023
1090
|
} else {
|
|
1024
1091
|
const parsedOperator = parseOperator(node);
|
|
@@ -1027,45 +1094,6 @@
|
|
|
1027
1094
|
parts.push(parsedOperator);
|
|
1028
1095
|
}
|
|
1029
1096
|
}
|
|
1030
|
-
|
|
1031
|
-
// --- START PATCH V6 (Giữ nguyên logic V5) ---
|
|
1032
|
-
} else if (NodeTool.getNodeName(node) === "msub") {
|
|
1033
|
-
const subChildren = Array.from(NodeTool.getChildren(node));
|
|
1034
|
-
if (
|
|
1035
|
-
subChildren.length === 2 &&
|
|
1036
|
-
NodeTool.getNodeName(subChildren[0]) === "mo" &&
|
|
1037
|
-
NodeTool.getNodeText(subChildren[0]).trim() === ")"
|
|
1038
|
-
) {
|
|
1039
|
-
// ĐÚNG LÀ NGOẠI LỆ
|
|
1040
|
-
|
|
1041
|
-
const sub = parse(subChildren[1]);
|
|
1042
|
-
// Mảng 'parts' lúc này "sạch" và là: ["\text{Cu}", "\left(", "\text{OH}"]
|
|
1043
|
-
const lastPart = parts.pop(); // lastPart = "\text{OH}"
|
|
1044
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\left("]
|
|
1045
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1046
|
-
if (parts[i] && parts[i].trim() === "\\left(") {
|
|
1047
|
-
parts.splice(i, 1); // Xóa "\left("
|
|
1048
|
-
break;
|
|
1049
|
-
}
|
|
1050
|
-
_;
|
|
1051
|
-
} // Mảng 'parts' lúc này là: ["\text{Cu}"]
|
|
1052
|
-
|
|
1053
|
-
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1054
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1055
|
-
|
|
1056
|
-
const nearLeftObj = lefts[lefts.length - 1];
|
|
1057
|
-
if (nearLeftObj) {
|
|
1058
|
-
lefts.pop();
|
|
1059
|
-
}
|
|
1060
|
-
} else {
|
|
1061
|
-
// <msub> bình thường
|
|
1062
|
-
const parsed = parse(node);
|
|
1063
|
-
if (parsed) {
|
|
1064
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1065
|
-
parts.push(parsed);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
// --- END PATCH V6 ---
|
|
1069
1097
|
} else {
|
|
1070
1098
|
// Các node khác như <mtext>, #text, v.v.
|
|
1071
1099
|
const parsed = parse(node);
|
|
@@ -1077,40 +1105,13 @@
|
|
|
1077
1105
|
});
|
|
1078
1106
|
|
|
1079
1107
|
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1080
|
-
//
|
|
1108
|
+
// để tránh sinh LaTeX không hợp lệ (\left mà không có \right)
|
|
1081
1109
|
if (lefts && lefts.length > 0) {
|
|
1082
|
-
const rightForLeft = (left) => {
|
|
1083
|
-
switch (left) {
|
|
1084
|
-
case "(":
|
|
1085
|
-
return ")";
|
|
1086
|
-
case "[":
|
|
1087
|
-
return "]";
|
|
1088
|
-
case "{":
|
|
1089
|
-
return "}";
|
|
1090
|
-
case "|":
|
|
1091
|
-
return "|";
|
|
1092
|
-
default:
|
|
1093
|
-
return ".";
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1096
|
-
|
|
1097
1110
|
lefts.forEach((leftObj) => {
|
|
1098
1111
|
const idx = leftObj && leftObj.index;
|
|
1099
1112
|
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
const right = rightForLeft(left);
|
|
1103
|
-
|
|
1104
|
-
// 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
|
|
1105
|
-
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1106
|
-
const hasMatchingRight =
|
|
1107
|
-
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1108
|
-
|
|
1109
|
-
if (!hasMatchingRight) {
|
|
1110
|
-
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1111
|
-
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1112
|
-
}
|
|
1113
|
-
// 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, "");
|
|
1114
1115
|
});
|
|
1115
1116
|
}
|
|
1116
1117
|
|
|
@@ -1118,6 +1119,44 @@
|
|
|
1118
1119
|
return parts;
|
|
1119
1120
|
}
|
|
1120
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
|
+
|
|
1121
1160
|
function getRender(node) {
|
|
1122
1161
|
let render = undefined;
|
|
1123
1162
|
const nodeName = NodeTool.getNodeName(node);
|
|
@@ -1266,9 +1305,11 @@
|
|
|
1266
1305
|
NodeTool.getNodeText(sub.firstChild).trim() === "")
|
|
1267
1306
|
) {
|
|
1268
1307
|
const lastChild = sub.lastChild;
|
|
1269
|
-
return lastChild
|
|
1308
|
+
return lastChild
|
|
1309
|
+
? `${wrapBaseForScript(base)}_{${parse(lastChild)}}`
|
|
1310
|
+
: base;
|
|
1270
1311
|
}
|
|
1271
|
-
return `${base}_{${parse(sub)}}`;
|
|
1312
|
+
return `${wrapBaseForScript(base)}_{${parse(sub)}}`;
|
|
1272
1313
|
};
|
|
1273
1314
|
break;
|
|
1274
1315
|
|
|
@@ -1276,22 +1317,9 @@
|
|
|
1276
1317
|
render = function (node, children) {
|
|
1277
1318
|
const childrenArray = Array.from(children);
|
|
1278
1319
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1279
|
-
|
|
1280
|
-
const baseNode = childrenArray[0];
|
|
1281
|
-
let base = parse(baseNode) || "";
|
|
1282
|
-
if (NodeTool.getNodeName(baseNode) === "mo") {
|
|
1283
|
-
const op = NodeTool.getNodeText(baseNode).trim();
|
|
1284
|
-
if (Brackets.isRight(op)) {
|
|
1285
|
-
// Escape brace characters
|
|
1286
|
-
if (op === "}" || op === "{") {
|
|
1287
|
-
base = `\\right\\${op}`;
|
|
1288
|
-
} else {
|
|
1289
|
-
base = `\\right${op}`;
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1320
|
+
const base = parse(childrenArray[0]) || "";
|
|
1293
1321
|
const sup = parse(childrenArray[1]) || "";
|
|
1294
|
-
return `${base}^{${sup}}`;
|
|
1322
|
+
return `${wrapBaseForScript(base)}^{${sup}}`;
|
|
1295
1323
|
};
|
|
1296
1324
|
break;
|
|
1297
1325
|
|
|
@@ -1315,7 +1343,7 @@
|
|
|
1315
1343
|
.join("");
|
|
1316
1344
|
return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
|
|
1317
1345
|
}
|
|
1318
|
-
return `${base}_{${sub}}^{${sup}}`;
|
|
1346
|
+
return `${wrapBaseForScript(base)}_{${sub}}^{${sup}}`;
|
|
1319
1347
|
};
|
|
1320
1348
|
break;
|
|
1321
1349
|
|
|
@@ -1325,9 +1353,15 @@
|
|
|
1325
1353
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1326
1354
|
const base = parse(childrenArray[0]) || "";
|
|
1327
1355
|
const over = parse(childrenArray[1]) || "";
|
|
1328
|
-
const
|
|
1356
|
+
const overNode = childrenArray[1];
|
|
1357
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1358
|
+
const overText = NodeTool.getNodeText(overNode)?.trim() || "";
|
|
1329
1359
|
const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
|
|
1330
1360
|
|
|
1361
|
+
// Handle arrows with extensible commands
|
|
1362
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow{${over}}`;
|
|
1363
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow{${over}}`;
|
|
1364
|
+
|
|
1331
1365
|
// Handle biology notation (double overline)
|
|
1332
1366
|
if (overText === "¯") {
|
|
1333
1367
|
const parentNode = node.parentNode;
|
|
@@ -1343,6 +1377,27 @@
|
|
|
1343
1377
|
|
|
1344
1378
|
if (overText === "→" && isAccent) return `\\vec{${base}}`;
|
|
1345
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
|
+
}
|
|
1346
1401
|
return `\\overset{${over}}{${base}}`;
|
|
1347
1402
|
};
|
|
1348
1403
|
break;
|
|
@@ -1353,10 +1408,21 @@
|
|
|
1353
1408
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1354
1409
|
const base = parse(childrenArray[0]) || "";
|
|
1355
1410
|
const under = parse(childrenArray[1]) || "";
|
|
1411
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1412
|
+
const underText = NodeTool.getNodeText(childrenArray[1])?.trim() || "";
|
|
1356
1413
|
const isUnderAccent =
|
|
1357
1414
|
NodeTool.getAttr(node, "accentunder", "false") === "true";
|
|
1358
1415
|
|
|
1416
|
+
// Handle arrows with extensible commands
|
|
1417
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{}`;
|
|
1418
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{}`;
|
|
1419
|
+
|
|
1359
1420
|
if (base === "∫") return `\\int_{${under}}`;
|
|
1421
|
+
if (underText === "\u23DF") return `\\underbrace{${base}}`;
|
|
1422
|
+
|
|
1423
|
+
if (isLimitOperator(base)) {
|
|
1424
|
+
return `${base}\\limits_{${under}}`;
|
|
1425
|
+
}
|
|
1360
1426
|
return `\\underset{${under}}{${base}}`;
|
|
1361
1427
|
};
|
|
1362
1428
|
break;
|
|
@@ -1370,19 +1436,19 @@
|
|
|
1370
1436
|
const over = parse(childrenArray[2]);
|
|
1371
1437
|
const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
|
|
1372
1438
|
|
|
1373
|
-
// Special handling for chemical reaction arrow
|
|
1374
|
-
if (
|
|
1375
|
-
|
|
1376
|
-
NodeTool.getNodeName(childrenArray[1]) === "msup"
|
|
1377
|
-
) {
|
|
1378
|
-
return `\\xrightarrow[${under}]{${over}}`;
|
|
1379
|
-
}
|
|
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}}`;
|
|
1380
1442
|
|
|
1381
1443
|
if (baseText === "∫") return `\\int_{${under}}^{${over}}`;
|
|
1382
1444
|
if (baseText === "∑") return `\\sum_{${under}}^{${over}}`;
|
|
1383
1445
|
if (baseText === "∏") return `\\prod_{${under}}^{${over}}`;
|
|
1384
1446
|
if (baseText === "|") return `\\big|_{${under}}^{${over}}`;
|
|
1385
|
-
|
|
1447
|
+
|
|
1448
|
+
if (isLimitOperator(base)) {
|
|
1449
|
+
return `${base}\\limits_{${under}}^{${over}}`;
|
|
1450
|
+
}
|
|
1451
|
+
return `\\underset{${under}}{\\overset{${over}}{${base}}}`;
|
|
1386
1452
|
};
|
|
1387
1453
|
break;
|
|
1388
1454
|
|
|
@@ -1390,30 +1456,56 @@
|
|
|
1390
1456
|
render = function (node, children) {
|
|
1391
1457
|
const childrenArray = Array.from(children);
|
|
1392
1458
|
if (!childrenArray || childrenArray.length < 1) return "";
|
|
1393
|
-
const base = parse(childrenArray[0]);
|
|
1394
|
-
|
|
1395
|
-
|
|
1459
|
+
const base = parse(childrenArray[0]) || "";
|
|
1460
|
+
|
|
1461
|
+
const postSub = [];
|
|
1462
|
+
const postSup = [];
|
|
1463
|
+
const preSub = [];
|
|
1464
|
+
const preSup = [];
|
|
1465
|
+
|
|
1396
1466
|
let i = 1;
|
|
1467
|
+
let inPrescripts = false;
|
|
1397
1468
|
|
|
1398
1469
|
while (i < childrenArray.length) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
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);
|
|
1407
1487
|
} else {
|
|
1408
|
-
if (
|
|
1409
|
-
|
|
1410
|
-
childrenArray[i + 1]
|
|
1411
|
-
)}}`;
|
|
1412
|
-
i += 2;
|
|
1413
|
-
} else break;
|
|
1488
|
+
if (sub) postSub.push(sub);
|
|
1489
|
+
if (sup) postSup.push(sup);
|
|
1414
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
i += 2;
|
|
1415
1493
|
}
|
|
1416
|
-
|
|
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}`;
|
|
1417
1509
|
};
|
|
1418
1510
|
break;
|
|
1419
1511
|
|
|
@@ -1514,7 +1606,28 @@
|
|
|
1514
1606
|
const num = parse(childrenArray[0]);
|
|
1515
1607
|
const den = parse(childrenArray[1]);
|
|
1516
1608
|
const linethickness = NodeTool.getAttr(node, "linethickness", "medium");
|
|
1517
|
-
|
|
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
|
+
|
|
1518
1631
|
return `\\frac{${num}}{${den}}`;
|
|
1519
1632
|
};
|
|
1520
1633
|
break;
|
|
@@ -1524,7 +1637,10 @@
|
|
|
1524
1637
|
const childrenArray = Array.from(children);
|
|
1525
1638
|
const open = NodeTool.getAttr(node, "open", "(");
|
|
1526
1639
|
const close = NodeTool.getAttr(node, "close", ")");
|
|
1527
|
-
const
|
|
1640
|
+
const separatorsStr = NodeTool.getAttr(node, "separators", ",");
|
|
1641
|
+
const separators = separatorsStr
|
|
1642
|
+
.split("")
|
|
1643
|
+
.filter((c) => c.trim().length === 1);
|
|
1528
1644
|
|
|
1529
1645
|
// Xử lý đặc biệt cho trường hợp dấu ngoặc đơn |
|
|
1530
1646
|
if (open === "|") {
|
|
@@ -1575,9 +1691,10 @@
|
|
|
1575
1691
|
parts.push(parse(child));
|
|
1576
1692
|
if (
|
|
1577
1693
|
index < childrenArray.length - 1 &&
|
|
1578
|
-
separators
|
|
1694
|
+
separators.length > 0
|
|
1579
1695
|
) {
|
|
1580
|
-
|
|
1696
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1697
|
+
if (sep) parts.push(sep);
|
|
1581
1698
|
}
|
|
1582
1699
|
});
|
|
1583
1700
|
return `\\left[${parts.join("")}\\right)`;
|
|
@@ -1589,18 +1706,20 @@
|
|
|
1589
1706
|
parts.push(parse(child));
|
|
1590
1707
|
if (
|
|
1591
1708
|
index < childrenArray.length - 1 &&
|
|
1592
|
-
separators
|
|
1709
|
+
separators.length > 0
|
|
1593
1710
|
) {
|
|
1594
|
-
|
|
1711
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1712
|
+
if (sep) parts.push(sep);
|
|
1595
1713
|
}
|
|
1596
1714
|
});
|
|
1597
1715
|
const content = parts.join("");
|
|
1598
1716
|
|
|
1599
1717
|
if (open === "{" && close === "}") return `\\{${content}\\}`;
|
|
1600
1718
|
if (open === "|" && close === "|") return `\\left|${content}\\right|`;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
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}`;
|
|
1604
1723
|
};
|
|
1605
1724
|
break;
|
|
1606
1725
|
|
|
@@ -1626,10 +1745,17 @@
|
|
|
1626
1745
|
case "mn":
|
|
1627
1746
|
case "mo":
|
|
1628
1747
|
case "ms":
|
|
1629
|
-
case "mtext":
|
|
1630
1748
|
render = getRender_joinSeparator("@content");
|
|
1631
1749
|
break;
|
|
1632
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
|
+
|
|
1633
1759
|
case "mphantom":
|
|
1634
1760
|
render = function (node, children) {
|
|
1635
1761
|
const childrenArray = Array.from(children);
|