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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Brackets = {
|
|
4
|
-
left: ['(', '[', '{', '|', '
|
|
5
|
-
right: [')', ']', '}', '|', '
|
|
4
|
+
left: ['(', '[', '{', '|', '\u2016', '\u27E8', '\u230A', '\u2308', '\u231C'],
|
|
5
|
+
right: [')', ']', '}', '|', '\u2016', '\u27E9', '\u230B', '\u2309', '\u231D'],
|
|
6
6
|
isPair: function(l, r){
|
|
7
7
|
const idx = this.left.indexOf(l);
|
|
8
8
|
return r === this.right[idx];
|
|
@@ -24,17 +24,17 @@ const Brackets = {
|
|
|
24
24
|
case '[':
|
|
25
25
|
case '|': r = `\\left${it}`;
|
|
26
26
|
break;
|
|
27
|
-
case '
|
|
27
|
+
case '\u2016': r = '\\left\\|';
|
|
28
28
|
break;
|
|
29
29
|
case '{': r = '\\left\\{';
|
|
30
30
|
break;
|
|
31
|
-
case '
|
|
31
|
+
case '\u27E8': r = '\\left\\langle ';
|
|
32
32
|
break;
|
|
33
|
-
case '
|
|
33
|
+
case '\u230A': r = '\\left\\lfloor ';
|
|
34
34
|
break;
|
|
35
|
-
case '
|
|
35
|
+
case '\u2308': r = '\\left\\lceil ';
|
|
36
36
|
break;
|
|
37
|
-
case '
|
|
37
|
+
case '\u231C': r = '\\left\\ulcorner ';
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
40
40
|
return (stretchy ? r : r.replace('\\left', ''));
|
|
@@ -48,23 +48,36 @@ const Brackets = {
|
|
|
48
48
|
case ']':
|
|
49
49
|
case '|': r = `\\right${it}`;
|
|
50
50
|
break;
|
|
51
|
-
case '
|
|
51
|
+
case '\u2016': r = '\\right\\|';
|
|
52
52
|
break;
|
|
53
53
|
case '}': r = '\\right\\}';
|
|
54
54
|
break;
|
|
55
|
-
case '
|
|
55
|
+
case '\u27E9': r = ' \\right\\rangle';
|
|
56
56
|
break;
|
|
57
|
-
case '
|
|
57
|
+
case '\u230B': r = ' \\right\\rfloor';
|
|
58
58
|
break;
|
|
59
|
-
case '
|
|
59
|
+
case '\u2309': r = ' \\right\\rceil';
|
|
60
60
|
break;
|
|
61
|
-
case '
|
|
61
|
+
case '\u231D': r = ' \\right\\urcorner';
|
|
62
62
|
break;
|
|
63
63
|
}
|
|
64
64
|
return (stretchy ? r : r.replace('\\right', ''));
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
var _nodeResolve_empty = {};
|
|
69
|
+
|
|
70
|
+
var _nodeResolve_empty$1 = /*#__PURE__*/Object.freeze({
|
|
71
|
+
__proto__: null,
|
|
72
|
+
'default': _nodeResolve_empty
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function getCjsExportFromNamespace (n) {
|
|
76
|
+
return n && n['default'] || n;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
var require$$0 = getCjsExportFromNamespace(_nodeResolve_empty$1);
|
|
80
|
+
|
|
68
81
|
/*
|
|
69
82
|
* Set up window for Node.js
|
|
70
83
|
*/
|
|
@@ -94,11 +107,17 @@ function canParseHTMLNatively () {
|
|
|
94
107
|
function createHTMLParser () {
|
|
95
108
|
const Parser = function () {};
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
const hasDocument =
|
|
111
|
+
typeof document !== 'undefined' &&
|
|
112
|
+
document &&
|
|
113
|
+
document.implementation &&
|
|
114
|
+
typeof document.implementation.createHTMLDocument === 'function';
|
|
115
|
+
|
|
116
|
+
if (hasDocument) {
|
|
117
|
+
if (typeof process !== 'undefined' && true && shouldUseActiveX()) {
|
|
99
118
|
Parser.prototype.parseFromString = function (string) {
|
|
100
119
|
const doc = new window.ActiveXObject('htmlfile');
|
|
101
|
-
doc.designMode = 'on';
|
|
120
|
+
doc.designMode = 'on';
|
|
102
121
|
doc.open();
|
|
103
122
|
doc.write(string);
|
|
104
123
|
doc.close();
|
|
@@ -115,11 +134,9 @@ function createHTMLParser () {
|
|
|
115
134
|
}
|
|
116
135
|
} else {
|
|
117
136
|
Parser.prototype.parseFromString = function (string) {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
doc.close();
|
|
122
|
-
return doc
|
|
137
|
+
const domino = require$$0;
|
|
138
|
+
const window = domino.createWindow(string || '');
|
|
139
|
+
return window.document
|
|
123
140
|
};
|
|
124
141
|
}
|
|
125
142
|
return Parser
|
|
@@ -137,11 +154,54 @@ function shouldUseActiveX () {
|
|
|
137
154
|
|
|
138
155
|
const HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
|
|
139
156
|
|
|
157
|
+
function normalizeMathMLInput(input) {
|
|
158
|
+
let s = input == null ? '' : String(input);
|
|
159
|
+
|
|
160
|
+
if (s.includes('<math') && /\\[ntr"]/.test(s)) {
|
|
161
|
+
s = s
|
|
162
|
+
.replace(/\\r\\n/g, '\n')
|
|
163
|
+
.replace(/\\n/g, '\n')
|
|
164
|
+
.replace(/\\t/g, '\t')
|
|
165
|
+
.replace(/\\"/g, '"')
|
|
166
|
+
.replace(/\\'/g, "'");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
s = s.replace(/^\s*<\?xml[\s\S]*?\?>\s*/i, '');
|
|
170
|
+
|
|
171
|
+
s = s.replace(
|
|
172
|
+
/xmlns\s*=\s*(["'])\s*`?\s*(https?:\/\/www\.w3\.org\/1998\/Math\/MathML)\s*`?\s*\1/gi,
|
|
173
|
+
'xmlns="$2"'
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
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"');
|
|
177
|
+
|
|
178
|
+
if (!/<math\b/i.test(s) && /<(msub|mrow|mi|mn|mo|mfrac|msup|msubsup|munder|mover|munderover|mtable)\b/i.test(s)) {
|
|
179
|
+
s = `<math xmlns="http://www.w3.org/1998/Math/MathML">${s}</math>`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return s;
|
|
183
|
+
}
|
|
184
|
+
|
|
140
185
|
const NodeTool = {
|
|
141
186
|
parseMath: function(html) {
|
|
187
|
+
const normalized = normalizeMathMLInput(html);
|
|
142
188
|
const parser = new HTMLParser();
|
|
143
|
-
const doc = parser.parseFromString(
|
|
144
|
-
|
|
189
|
+
const doc = parser.parseFromString(normalized, 'text/html');
|
|
190
|
+
let math = doc && doc.querySelector ? doc.querySelector('math') : null;
|
|
191
|
+
|
|
192
|
+
if (!math) {
|
|
193
|
+
const match = normalized.match(/<math\b[\s\S]*?<\/math>/i);
|
|
194
|
+
if (match) {
|
|
195
|
+
const retryDoc = parser.parseFromString(match[0], 'text/html');
|
|
196
|
+
math = retryDoc && retryDoc.querySelector ? retryDoc.querySelector('math') : null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!math) {
|
|
201
|
+
throw new Error('Invalid MathML: missing <math> root element');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return math;
|
|
145
205
|
},
|
|
146
206
|
getChildren: function(node) {
|
|
147
207
|
return node.children;
|
|
@@ -615,7 +675,7 @@ function convert(mathmlHtml) {
|
|
|
615
675
|
.replace(/∫/g, " \\int "); // Tích phân
|
|
616
676
|
|
|
617
677
|
// Đả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)
|
|
618
|
-
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
678
|
+
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim(?!its)|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
619
679
|
|
|
620
680
|
return result;
|
|
621
681
|
}
|
|
@@ -761,18 +821,21 @@ function parseOperator(node) {
|
|
|
761
821
|
"±": " \\pm ",
|
|
762
822
|
"×": " \\times ",
|
|
763
823
|
"÷": " \\div ",
|
|
764
|
-
"∑": "
|
|
765
|
-
"∏": "
|
|
766
|
-
"∫": "
|
|
824
|
+
"∑": "\\sum ",
|
|
825
|
+
"∏": "\\prod ",
|
|
826
|
+
"∫": "\\int ",
|
|
767
827
|
"−": "-",
|
|
768
828
|
"≠": " \\neq ",
|
|
769
829
|
">": " > ",
|
|
770
830
|
"=": " = ",
|
|
831
|
+
"(": "(",
|
|
832
|
+
")": ")",
|
|
771
833
|
",": ", ", // Dấu phẩy trong tập hợp
|
|
772
834
|
";": ";",
|
|
773
835
|
Ω: "\\Omega",
|
|
774
836
|
"|": " \\mid ", // PATCH: set-builder mid
|
|
775
837
|
π: " \\pi ", // PATCH: Greek letter
|
|
838
|
+
"...": "\\dots",
|
|
776
839
|
};
|
|
777
840
|
const res = operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
|
|
778
841
|
return res;
|
|
@@ -849,8 +912,8 @@ function parseElementMtext(node) {
|
|
|
849
912
|
// Nếu đã là lệnh LaTeX thì giữ nguyên
|
|
850
913
|
if (content.startsWith("\\")) return content;
|
|
851
914
|
|
|
852
|
-
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH,
|
|
853
|
-
if (/^[A-Za-z][A-Za-z0-9]*$/.test(content)) {
|
|
915
|
+
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH, ..., k times)
|
|
916
|
+
if (/^[A-Za-z][A-Za-z0-9\s]*$/.test(content)) {
|
|
854
917
|
return `\\text{${content}}`;
|
|
855
918
|
}
|
|
856
919
|
|
|
@@ -905,6 +968,22 @@ function escapeSpecialChars(text) {
|
|
|
905
968
|
return text;
|
|
906
969
|
}
|
|
907
970
|
|
|
971
|
+
function wrapBaseForScript(base) {
|
|
972
|
+
const t = (base ?? "").trim();
|
|
973
|
+
if (!t) return "";
|
|
974
|
+
if (t.startsWith("{") && t.endsWith("}")) return t;
|
|
975
|
+
if (/^\\[a-zA-Z]+$/.test(t)) return t;
|
|
976
|
+
const needsWrap =
|
|
977
|
+
t.includes("_") ||
|
|
978
|
+
t.includes("^") ||
|
|
979
|
+
/\s/.test(t) ||
|
|
980
|
+
t.startsWith("\\left") ||
|
|
981
|
+
t.startsWith("\\right") ||
|
|
982
|
+
/\\[a-zA-Z]+/.test(t) ||
|
|
983
|
+
/[{}]/.test(t);
|
|
984
|
+
return needsWrap ? `{${t}}` : t;
|
|
985
|
+
}
|
|
986
|
+
|
|
908
987
|
function parseContainer(node, children) {
|
|
909
988
|
const render = getRender(node);
|
|
910
989
|
if (render) {
|
|
@@ -923,22 +1002,6 @@ function renderChildren(children) {
|
|
|
923
1002
|
// lefts là mảng object: { op: "(", index: 5 }
|
|
924
1003
|
let lefts = [];
|
|
925
1004
|
|
|
926
|
-
if (
|
|
927
|
-
children.length >= 3 &&
|
|
928
|
-
NodeTool.getNodeName(children[0]) === "mo" &&
|
|
929
|
-
NodeTool.getNodeText(children[0]).trim() === "{" &&
|
|
930
|
-
NodeTool.getNodeName(children[children.length - 1]) === "mo" &&
|
|
931
|
-
NodeTool.getNodeText(children[children.length - 1]).trim() === "}"
|
|
932
|
-
) {
|
|
933
|
-
// Render inner content
|
|
934
|
-
const innerContent = Array.prototype.slice
|
|
935
|
-
.call(children, 1, -1)
|
|
936
|
-
.map((child) => parse(child))
|
|
937
|
-
.join(""); // Chỉ trả về nếu nội dung không rỗng
|
|
938
|
-
const result = `\\left\\{${innerContent}\\right\\}`;
|
|
939
|
-
return result;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
1005
|
Array.prototype.forEach.call(children, (node, idx) => {
|
|
943
1006
|
// PATCH: Thin space between variables/numbers in mfrac numerator (k 2 π)
|
|
944
1007
|
if (
|
|
@@ -966,55 +1029,59 @@ function renderChildren(children) {
|
|
|
966
1029
|
if (Brackets.contains(op)) {
|
|
967
1030
|
let stretchy = NodeTool.getAttr(node, "stretchy", "true");
|
|
968
1031
|
stretchy = ["", "true"].indexOf(stretchy) > -1;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
if (
|
|
972
|
-
|
|
1032
|
+
const parentNode = node.parentNode;
|
|
1033
|
+
const isInPower = parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
1034
|
+
if (isInPower) {
|
|
1035
|
+
stretchy = false;
|
|
973
1036
|
}
|
|
974
1037
|
|
|
975
1038
|
if (Brackets.isRight(op)) {
|
|
976
1039
|
const nearLeftObj = lefts[lefts.length - 1];
|
|
977
|
-
if (nearLeftObj) {
|
|
978
|
-
const
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
parts.
|
|
1040
|
+
if (nearLeftObj && Brackets.isPair(nearLeftObj.op, op)) {
|
|
1041
|
+
const leftIdx = nearLeftObj.index;
|
|
1042
|
+
const leftRendered =
|
|
1043
|
+
typeof leftIdx === "number" ? String(parts[leftIdx] ?? "") : "";
|
|
1044
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1045
|
+
|
|
1046
|
+
const leftRenderedTrimmed = leftRendered.trim();
|
|
1047
|
+
const rightRenderedTrimmed = String(rightRendered).trim();
|
|
1048
|
+
|
|
1049
|
+
if (
|
|
1050
|
+
leftRenderedTrimmed.startsWith("\\left") &&
|
|
1051
|
+
!rightRenderedTrimmed.startsWith("\\right")
|
|
1052
|
+
) {
|
|
1053
|
+
parts[leftIdx] = leftRendered.replace("\\left", "");
|
|
1054
|
+
} else if (
|
|
1055
|
+
!leftRenderedTrimmed.startsWith("\\left") &&
|
|
1056
|
+
rightRenderedTrimmed.startsWith("\\right")
|
|
1057
|
+
) {
|
|
1058
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
991
1059
|
}
|
|
992
|
-
|
|
1060
|
+
|
|
1061
|
+
if (rightRendered) parts.push(rightRendered);
|
|
993
1062
|
lefts.pop();
|
|
1063
|
+
} else if (Brackets.isLeft(op)) {
|
|
1064
|
+
// If it's a Right bracket but doesn't match the current Left,
|
|
1065
|
+
// AND it is also a Left bracket (e.g. '|'), treat it as a new Left.
|
|
1066
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1067
|
+
if (partToPush) parts.push(partToPush);
|
|
1068
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
994
1069
|
} else {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1070
|
+
// Unmatched right bracket
|
|
1071
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1072
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
1073
|
+
if (rightRendered) parts.push(rightRendered);
|
|
999
1074
|
}
|
|
1000
1075
|
} else {
|
|
1001
|
-
//
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
let partToPush = "";
|
|
1007
|
-
if (stretchy && !isInPower) {
|
|
1008
|
-
partToPush = `\\left${escapedOp}`;
|
|
1076
|
+
// Must be Left bracket (or only Left)
|
|
1077
|
+
if (Brackets.isLeft(op)) {
|
|
1078
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1079
|
+
if (partToPush) parts.push(partToPush);
|
|
1080
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
1009
1081
|
} else {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (partToPush) {
|
|
1013
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1014
|
-
parts.push(partToPush);
|
|
1082
|
+
// Should not happen if Brackets.contains is correct
|
|
1083
|
+
if (op) parts.push(op);
|
|
1015
1084
|
}
|
|
1016
|
-
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1017
|
-
lefts.push({ op: op, index: parts.length - 1 });
|
|
1018
1085
|
}
|
|
1019
1086
|
} else {
|
|
1020
1087
|
const parsedOperator = parseOperator(node);
|
|
@@ -1023,45 +1090,6 @@ function renderChildren(children) {
|
|
|
1023
1090
|
parts.push(parsedOperator);
|
|
1024
1091
|
}
|
|
1025
1092
|
}
|
|
1026
|
-
|
|
1027
|
-
// --- START PATCH V6 (Giữ nguyên logic V5) ---
|
|
1028
|
-
} else if (NodeTool.getNodeName(node) === "msub") {
|
|
1029
|
-
const subChildren = Array.from(NodeTool.getChildren(node));
|
|
1030
|
-
if (
|
|
1031
|
-
subChildren.length === 2 &&
|
|
1032
|
-
NodeTool.getNodeName(subChildren[0]) === "mo" &&
|
|
1033
|
-
NodeTool.getNodeText(subChildren[0]).trim() === ")"
|
|
1034
|
-
) {
|
|
1035
|
-
// ĐÚNG LÀ NGOẠI LỆ
|
|
1036
|
-
|
|
1037
|
-
const sub = parse(subChildren[1]);
|
|
1038
|
-
// Mảng 'parts' lúc này "sạch" và là: ["\text{Cu}", "\left(", "\text{OH}"]
|
|
1039
|
-
const lastPart = parts.pop(); // lastPart = "\text{OH}"
|
|
1040
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\left("]
|
|
1041
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
1042
|
-
if (parts[i] && parts[i].trim() === "\\left(") {
|
|
1043
|
-
parts.splice(i, 1); // Xóa "\left("
|
|
1044
|
-
break;
|
|
1045
|
-
}
|
|
1046
|
-
_;
|
|
1047
|
-
} // Mảng 'parts' lúc này là: ["\text{Cu}"]
|
|
1048
|
-
|
|
1049
|
-
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1050
|
-
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1051
|
-
|
|
1052
|
-
const nearLeftObj = lefts[lefts.length - 1];
|
|
1053
|
-
if (nearLeftObj) {
|
|
1054
|
-
lefts.pop();
|
|
1055
|
-
}
|
|
1056
|
-
} else {
|
|
1057
|
-
// <msub> bình thường
|
|
1058
|
-
const parsed = parse(node);
|
|
1059
|
-
if (parsed) {
|
|
1060
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1061
|
-
parts.push(parsed);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
// --- END PATCH V6 ---
|
|
1065
1093
|
} else {
|
|
1066
1094
|
// Các node khác như <mtext>, #text, v.v.
|
|
1067
1095
|
const parsed = parse(node);
|
|
@@ -1073,40 +1101,13 @@ function renderChildren(children) {
|
|
|
1073
1101
|
});
|
|
1074
1102
|
|
|
1075
1103
|
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1076
|
-
//
|
|
1104
|
+
// để tránh sinh LaTeX không hợp lệ (\left mà không có \right)
|
|
1077
1105
|
if (lefts && lefts.length > 0) {
|
|
1078
|
-
const rightForLeft = (left) => {
|
|
1079
|
-
switch (left) {
|
|
1080
|
-
case "(":
|
|
1081
|
-
return ")";
|
|
1082
|
-
case "[":
|
|
1083
|
-
return "]";
|
|
1084
|
-
case "{":
|
|
1085
|
-
return "}";
|
|
1086
|
-
case "|":
|
|
1087
|
-
return "|";
|
|
1088
|
-
default:
|
|
1089
|
-
return ".";
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
|
|
1093
1106
|
lefts.forEach((leftObj) => {
|
|
1094
1107
|
const idx = leftObj && leftObj.index;
|
|
1095
1108
|
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const right = rightForLeft(left);
|
|
1099
|
-
|
|
1100
|
-
// 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
|
|
1101
|
-
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1102
|
-
const hasMatchingRight =
|
|
1103
|
-
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1104
|
-
|
|
1105
|
-
if (!hasMatchingRight) {
|
|
1106
|
-
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1107
|
-
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1108
|
-
}
|
|
1109
|
-
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1109
|
+
// Use regex with whitespace support and global flag just in case
|
|
1110
|
+
parts[idx] = String(parts[idx]).replace(/\\left/g, "");
|
|
1110
1111
|
});
|
|
1111
1112
|
}
|
|
1112
1113
|
|
|
@@ -1114,6 +1115,44 @@ function renderChildren(children) {
|
|
|
1114
1115
|
return parts;
|
|
1115
1116
|
}
|
|
1116
1117
|
|
|
1118
|
+
function isLimitOperator(base) {
|
|
1119
|
+
const t = (base || "").trim();
|
|
1120
|
+
const ops = [
|
|
1121
|
+
"\\sum",
|
|
1122
|
+
"\\prod",
|
|
1123
|
+
"\\coprod",
|
|
1124
|
+
"\\int",
|
|
1125
|
+
"\\oint",
|
|
1126
|
+
"\\bigcap",
|
|
1127
|
+
"\\bigcup",
|
|
1128
|
+
"\\bigsqcup",
|
|
1129
|
+
"\\bigvee",
|
|
1130
|
+
"\\bigwedge",
|
|
1131
|
+
"\\bigodot",
|
|
1132
|
+
"\\bigotimes",
|
|
1133
|
+
"\\bigoplus",
|
|
1134
|
+
"\\biguplus",
|
|
1135
|
+
"\\lim",
|
|
1136
|
+
"\\max",
|
|
1137
|
+
"\\min",
|
|
1138
|
+
"\\sup",
|
|
1139
|
+
"\\inf",
|
|
1140
|
+
"\\det",
|
|
1141
|
+
"\\gcd",
|
|
1142
|
+
"\\Pr",
|
|
1143
|
+
"\\limsup",
|
|
1144
|
+
"\\liminf",
|
|
1145
|
+
];
|
|
1146
|
+
return ops.some(
|
|
1147
|
+
(op) =>
|
|
1148
|
+
t === op ||
|
|
1149
|
+
t.startsWith(op + " ") ||
|
|
1150
|
+
t.startsWith(op + "\\") ||
|
|
1151
|
+
t.startsWith(op + "^") ||
|
|
1152
|
+
t.startsWith(op + "_")
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1117
1156
|
function getRender(node) {
|
|
1118
1157
|
let render = undefined;
|
|
1119
1158
|
const nodeName = NodeTool.getNodeName(node);
|
|
@@ -1262,9 +1301,11 @@ function getRender(node) {
|
|
|
1262
1301
|
NodeTool.getNodeText(sub.firstChild).trim() === "")
|
|
1263
1302
|
) {
|
|
1264
1303
|
const lastChild = sub.lastChild;
|
|
1265
|
-
return lastChild
|
|
1304
|
+
return lastChild
|
|
1305
|
+
? `${wrapBaseForScript(base)}_{${parse(lastChild)}}`
|
|
1306
|
+
: base;
|
|
1266
1307
|
}
|
|
1267
|
-
return `${base}_{${parse(sub)}}`;
|
|
1308
|
+
return `${wrapBaseForScript(base)}_{${parse(sub)}}`;
|
|
1268
1309
|
};
|
|
1269
1310
|
break;
|
|
1270
1311
|
|
|
@@ -1272,22 +1313,9 @@ function getRender(node) {
|
|
|
1272
1313
|
render = function (node, children) {
|
|
1273
1314
|
const childrenArray = Array.from(children);
|
|
1274
1315
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1275
|
-
|
|
1276
|
-
const baseNode = childrenArray[0];
|
|
1277
|
-
let base = parse(baseNode) || "";
|
|
1278
|
-
if (NodeTool.getNodeName(baseNode) === "mo") {
|
|
1279
|
-
const op = NodeTool.getNodeText(baseNode).trim();
|
|
1280
|
-
if (Brackets.isRight(op)) {
|
|
1281
|
-
// Escape brace characters
|
|
1282
|
-
if (op === "}" || op === "{") {
|
|
1283
|
-
base = `\\right\\${op}`;
|
|
1284
|
-
} else {
|
|
1285
|
-
base = `\\right${op}`;
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1316
|
+
const base = parse(childrenArray[0]) || "";
|
|
1289
1317
|
const sup = parse(childrenArray[1]) || "";
|
|
1290
|
-
return `${base}^{${sup}}`;
|
|
1318
|
+
return `${wrapBaseForScript(base)}^{${sup}}`;
|
|
1291
1319
|
};
|
|
1292
1320
|
break;
|
|
1293
1321
|
|
|
@@ -1311,7 +1339,7 @@ function getRender(node) {
|
|
|
1311
1339
|
.join("");
|
|
1312
1340
|
return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
|
|
1313
1341
|
}
|
|
1314
|
-
return `${base}_{${sub}}^{${sup}}`;
|
|
1342
|
+
return `${wrapBaseForScript(base)}_{${sub}}^{${sup}}`;
|
|
1315
1343
|
};
|
|
1316
1344
|
break;
|
|
1317
1345
|
|
|
@@ -1321,9 +1349,15 @@ function getRender(node) {
|
|
|
1321
1349
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1322
1350
|
const base = parse(childrenArray[0]) || "";
|
|
1323
1351
|
const over = parse(childrenArray[1]) || "";
|
|
1324
|
-
const
|
|
1352
|
+
const overNode = childrenArray[1];
|
|
1353
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1354
|
+
const overText = NodeTool.getNodeText(overNode)?.trim() || "";
|
|
1325
1355
|
const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
|
|
1326
1356
|
|
|
1357
|
+
// Handle arrows with extensible commands
|
|
1358
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow{${over}}`;
|
|
1359
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow{${over}}`;
|
|
1360
|
+
|
|
1327
1361
|
// Handle biology notation (double overline)
|
|
1328
1362
|
if (overText === "¯") {
|
|
1329
1363
|
const parentNode = node.parentNode;
|
|
@@ -1339,6 +1373,27 @@ function getRender(node) {
|
|
|
1339
1373
|
|
|
1340
1374
|
if (overText === "→" && isAccent) return `\\vec{${base}}`;
|
|
1341
1375
|
if (overText === "^" && isAccent) return `\\hat{${base}}`;
|
|
1376
|
+
if (overText === "\u23DE") return `\\overbrace{${base}}`;
|
|
1377
|
+
|
|
1378
|
+
// Check for nested overbrace (layer-2)
|
|
1379
|
+
if (NodeTool.getNodeName(overNode) === "mover") {
|
|
1380
|
+
const innerChildren = Array.from(NodeTool.getChildren(overNode));
|
|
1381
|
+
if (innerChildren.length >= 2) {
|
|
1382
|
+
const innerBaseText = NodeTool.getNodeText(innerChildren[0]).trim();
|
|
1383
|
+
if (innerBaseText === "\u23DE") {
|
|
1384
|
+
const label = parse(innerChildren[1]);
|
|
1385
|
+
return `\\overbrace{${base}}\\limits^{${label}}`;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (base.startsWith("\\overbrace") || base.startsWith("\\underbrace")) {
|
|
1391
|
+
return `${base}\\limits^{${over}}`;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
if (isLimitOperator(base)) {
|
|
1395
|
+
return `${base}\\limits^{${over}}`;
|
|
1396
|
+
}
|
|
1342
1397
|
return `\\overset{${over}}{${base}}`;
|
|
1343
1398
|
};
|
|
1344
1399
|
break;
|
|
@@ -1349,10 +1404,21 @@ function getRender(node) {
|
|
|
1349
1404
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1350
1405
|
const base = parse(childrenArray[0]) || "";
|
|
1351
1406
|
const under = parse(childrenArray[1]) || "";
|
|
1407
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1408
|
+
const underText = NodeTool.getNodeText(childrenArray[1])?.trim() || "";
|
|
1352
1409
|
const isUnderAccent =
|
|
1353
1410
|
NodeTool.getAttr(node, "accentunder", "false") === "true";
|
|
1354
1411
|
|
|
1412
|
+
// Handle arrows with extensible commands
|
|
1413
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{}`;
|
|
1414
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{}`;
|
|
1415
|
+
|
|
1355
1416
|
if (base === "∫") return `\\int_{${under}}`;
|
|
1417
|
+
if (underText === "\u23DF") return `\\underbrace{${base}}`;
|
|
1418
|
+
|
|
1419
|
+
if (isLimitOperator(base)) {
|
|
1420
|
+
return `${base}\\limits_{${under}}`;
|
|
1421
|
+
}
|
|
1356
1422
|
return `\\underset{${under}}{${base}}`;
|
|
1357
1423
|
};
|
|
1358
1424
|
break;
|
|
@@ -1366,19 +1432,19 @@ function getRender(node) {
|
|
|
1366
1432
|
const over = parse(childrenArray[2]);
|
|
1367
1433
|
const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
|
|
1368
1434
|
|
|
1369
|
-
// Special handling for chemical reaction arrow
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
1372
|
-
NodeTool.getNodeName(childrenArray[1]) === "msup"
|
|
1373
|
-
) {
|
|
1374
|
-
return `\\xrightarrow[${under}]{${over}}`;
|
|
1375
|
-
}
|
|
1435
|
+
// Special handling for chemical reaction arrow and other arrows
|
|
1436
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{${over}}`;
|
|
1437
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{${over}}`;
|
|
1376
1438
|
|
|
1377
1439
|
if (baseText === "∫") return `\\int_{${under}}^{${over}}`;
|
|
1378
1440
|
if (baseText === "∑") return `\\sum_{${under}}^{${over}}`;
|
|
1379
1441
|
if (baseText === "∏") return `\\prod_{${under}}^{${over}}`;
|
|
1380
1442
|
if (baseText === "|") return `\\big|_{${under}}^{${over}}`;
|
|
1381
|
-
|
|
1443
|
+
|
|
1444
|
+
if (isLimitOperator(base)) {
|
|
1445
|
+
return `${base}\\limits_{${under}}^{${over}}`;
|
|
1446
|
+
}
|
|
1447
|
+
return `\\underset{${under}}{\\overset{${over}}{${base}}}`;
|
|
1382
1448
|
};
|
|
1383
1449
|
break;
|
|
1384
1450
|
|
|
@@ -1386,30 +1452,56 @@ function getRender(node) {
|
|
|
1386
1452
|
render = function (node, children) {
|
|
1387
1453
|
const childrenArray = Array.from(children);
|
|
1388
1454
|
if (!childrenArray || childrenArray.length < 1) return "";
|
|
1389
|
-
const base = parse(childrenArray[0]);
|
|
1390
|
-
|
|
1391
|
-
|
|
1455
|
+
const base = parse(childrenArray[0]) || "";
|
|
1456
|
+
|
|
1457
|
+
const postSub = [];
|
|
1458
|
+
const postSup = [];
|
|
1459
|
+
const preSub = [];
|
|
1460
|
+
const preSup = [];
|
|
1461
|
+
|
|
1392
1462
|
let i = 1;
|
|
1463
|
+
let inPrescripts = false;
|
|
1393
1464
|
|
|
1394
1465
|
while (i < childrenArray.length) {
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1466
|
+
const name = NodeTool.getNodeName(childrenArray[i]);
|
|
1467
|
+
if (name === "mprescripts") {
|
|
1468
|
+
inPrescripts = true;
|
|
1469
|
+
i += 1;
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const subNode = childrenArray[i];
|
|
1474
|
+
const supNode = childrenArray[i + 1];
|
|
1475
|
+
if (!subNode || !supNode) break;
|
|
1476
|
+
|
|
1477
|
+
const sub = parse(subNode) || "";
|
|
1478
|
+
const sup = parse(supNode) || "";
|
|
1479
|
+
|
|
1480
|
+
if (inPrescripts) {
|
|
1481
|
+
if (sub) preSub.push(sub);
|
|
1482
|
+
if (sup) preSup.push(sup);
|
|
1403
1483
|
} else {
|
|
1404
|
-
if (
|
|
1405
|
-
|
|
1406
|
-
childrenArray[i + 1]
|
|
1407
|
-
)}}`;
|
|
1408
|
-
i += 2;
|
|
1409
|
-
} else break;
|
|
1484
|
+
if (sub) postSub.push(sub);
|
|
1485
|
+
if (sup) postSup.push(sup);
|
|
1410
1486
|
}
|
|
1487
|
+
|
|
1488
|
+
i += 2;
|
|
1411
1489
|
}
|
|
1412
|
-
|
|
1490
|
+
|
|
1491
|
+
const preSubStr = preSub.join(" ");
|
|
1492
|
+
const preSupStr = preSup.join(" ");
|
|
1493
|
+
const postSubStr = postSub.join(" ");
|
|
1494
|
+
const postSupStr = postSup.join(" ");
|
|
1495
|
+
|
|
1496
|
+
let pre = "";
|
|
1497
|
+
if (preSubStr) pre += `_{${preSubStr}}`;
|
|
1498
|
+
if (preSupStr) pre += `^{${preSupStr}}`;
|
|
1499
|
+
|
|
1500
|
+
let post = "";
|
|
1501
|
+
if (postSubStr) post += `_{${postSubStr}}`;
|
|
1502
|
+
if (postSupStr) post += `^{${postSupStr}}`;
|
|
1503
|
+
|
|
1504
|
+
return `${pre}${wrapBaseForScript(base)}${post}`;
|
|
1413
1505
|
};
|
|
1414
1506
|
break;
|
|
1415
1507
|
|
|
@@ -1510,7 +1602,28 @@ function getRender(node) {
|
|
|
1510
1602
|
const num = parse(childrenArray[0]);
|
|
1511
1603
|
const den = parse(childrenArray[1]);
|
|
1512
1604
|
const linethickness = NodeTool.getAttr(node, "linethickness", "medium");
|
|
1513
|
-
|
|
1605
|
+
const bevelled = NodeTool.getAttr(node, "bevelled", "false");
|
|
1606
|
+
|
|
1607
|
+
if (bevelled === "true") {
|
|
1608
|
+
return `{}^{${num}}/_{${den}}`;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (["0", "0px"].indexOf(linethickness) > -1) {
|
|
1612
|
+
const prevNode = NodeTool.getPrevNode(node);
|
|
1613
|
+
const nextNode = NodeTool.getNextNode(node);
|
|
1614
|
+
if (
|
|
1615
|
+
prevNode &&
|
|
1616
|
+
NodeTool.getNodeName(prevNode) === "mo" &&
|
|
1617
|
+
NodeTool.getNodeText(prevNode).trim() === "(" &&
|
|
1618
|
+
nextNode &&
|
|
1619
|
+
NodeTool.getNodeName(nextNode) === "mo" &&
|
|
1620
|
+
NodeTool.getNodeText(nextNode).trim() === ")"
|
|
1621
|
+
) {
|
|
1622
|
+
return `\\DELETE_BRACKET_L\\binom{${num}}{${den}}\\DELETE_BRACKET_R`;
|
|
1623
|
+
}
|
|
1624
|
+
return `{}_{${den}}^{${num}}`;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1514
1627
|
return `\\frac{${num}}{${den}}`;
|
|
1515
1628
|
};
|
|
1516
1629
|
break;
|
|
@@ -1520,7 +1633,10 @@ function getRender(node) {
|
|
|
1520
1633
|
const childrenArray = Array.from(children);
|
|
1521
1634
|
const open = NodeTool.getAttr(node, "open", "(");
|
|
1522
1635
|
const close = NodeTool.getAttr(node, "close", ")");
|
|
1523
|
-
const
|
|
1636
|
+
const separatorsStr = NodeTool.getAttr(node, "separators", ",");
|
|
1637
|
+
const separators = separatorsStr
|
|
1638
|
+
.split("")
|
|
1639
|
+
.filter((c) => c.trim().length === 1);
|
|
1524
1640
|
|
|
1525
1641
|
// Xử lý đặc biệt cho trường hợp dấu ngoặc đơn |
|
|
1526
1642
|
if (open === "|") {
|
|
@@ -1571,9 +1687,10 @@ function getRender(node) {
|
|
|
1571
1687
|
parts.push(parse(child));
|
|
1572
1688
|
if (
|
|
1573
1689
|
index < childrenArray.length - 1 &&
|
|
1574
|
-
separators
|
|
1690
|
+
separators.length > 0
|
|
1575
1691
|
) {
|
|
1576
|
-
|
|
1692
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1693
|
+
if (sep) parts.push(sep);
|
|
1577
1694
|
}
|
|
1578
1695
|
});
|
|
1579
1696
|
return `\\left[${parts.join("")}\\right)`;
|
|
@@ -1585,18 +1702,20 @@ function getRender(node) {
|
|
|
1585
1702
|
parts.push(parse(child));
|
|
1586
1703
|
if (
|
|
1587
1704
|
index < childrenArray.length - 1 &&
|
|
1588
|
-
separators
|
|
1705
|
+
separators.length > 0
|
|
1589
1706
|
) {
|
|
1590
|
-
|
|
1707
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1708
|
+
if (sep) parts.push(sep);
|
|
1591
1709
|
}
|
|
1592
1710
|
});
|
|
1593
1711
|
const content = parts.join("");
|
|
1594
1712
|
|
|
1595
1713
|
if (open === "{" && close === "}") return `\\{${content}\\}`;
|
|
1596
1714
|
if (open === "|" && close === "|") return `\\left|${content}\\right|`;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
return
|
|
1715
|
+
const left = open ? Brackets.parseLeft(open) : "";
|
|
1716
|
+
const right = close ? Brackets.parseRight(close) : "";
|
|
1717
|
+
if (!close && open) return `${left}${content}\\right.`;
|
|
1718
|
+
return `${left}${content}${right}`;
|
|
1600
1719
|
};
|
|
1601
1720
|
break;
|
|
1602
1721
|
|
|
@@ -1622,10 +1741,17 @@ function getRender(node) {
|
|
|
1622
1741
|
case "mn":
|
|
1623
1742
|
case "mo":
|
|
1624
1743
|
case "ms":
|
|
1625
|
-
case "mtext":
|
|
1626
1744
|
render = getRender_joinSeparator("@content");
|
|
1627
1745
|
break;
|
|
1628
1746
|
|
|
1747
|
+
case "mtext":
|
|
1748
|
+
render = function (node, children) {
|
|
1749
|
+
const childrenArray = Array.from(children);
|
|
1750
|
+
const content = renderChildren(childrenArray).join("");
|
|
1751
|
+
return `\\text{${content}}`;
|
|
1752
|
+
};
|
|
1753
|
+
break;
|
|
1754
|
+
|
|
1629
1755
|
case "mphantom":
|
|
1630
1756
|
render = function (node, children) {
|
|
1631
1757
|
const childrenArray = Array.from(children);
|