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
package/lib/mathml2latex.umd.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
|
-
(global = global || self, global.MathML2LaTeX = factory());
|
|
5
|
-
}(this, (function () { 'use strict';
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('domino')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['domino'], factory) :
|
|
4
|
+
(global = global || self, global.MathML2LaTeX = factory(global.domino));
|
|
5
|
+
}(this, (function (domino) { 'use strict';
|
|
6
|
+
|
|
7
|
+
domino = domino && Object.prototype.hasOwnProperty.call(domino, 'default') ? domino['default'] : domino;
|
|
6
8
|
|
|
7
9
|
const Brackets = {
|
|
8
|
-
left: ['(', '[', '{', '|', '
|
|
9
|
-
right: [')', ']', '}', '|', '
|
|
10
|
+
left: ['(', '[', '{', '|', '\u2016', '\u27E8', '\u230A', '\u2308', '\u231C'],
|
|
11
|
+
right: [')', ']', '}', '|', '\u2016', '\u27E9', '\u230B', '\u2309', '\u231D'],
|
|
10
12
|
isPair: function(l, r){
|
|
11
13
|
const idx = this.left.indexOf(l);
|
|
12
14
|
return r === this.right[idx];
|
|
@@ -28,17 +30,17 @@
|
|
|
28
30
|
case '[':
|
|
29
31
|
case '|': r = `\\left${it}`;
|
|
30
32
|
break;
|
|
31
|
-
case '
|
|
33
|
+
case '\u2016': r = '\\left\\|';
|
|
32
34
|
break;
|
|
33
35
|
case '{': r = '\\left\\{';
|
|
34
36
|
break;
|
|
35
|
-
case '
|
|
37
|
+
case '\u27E8': r = '\\left\\langle ';
|
|
36
38
|
break;
|
|
37
|
-
case '
|
|
39
|
+
case '\u230A': r = '\\left\\lfloor ';
|
|
38
40
|
break;
|
|
39
|
-
case '
|
|
41
|
+
case '\u2308': r = '\\left\\lceil ';
|
|
40
42
|
break;
|
|
41
|
-
case '
|
|
43
|
+
case '\u231C': r = '\\left\\ulcorner ';
|
|
42
44
|
break;
|
|
43
45
|
}
|
|
44
46
|
return (stretchy ? r : r.replace('\\left', ''));
|
|
@@ -52,17 +54,17 @@
|
|
|
52
54
|
case ']':
|
|
53
55
|
case '|': r = `\\right${it}`;
|
|
54
56
|
break;
|
|
55
|
-
case '
|
|
57
|
+
case '\u2016': r = '\\right\\|';
|
|
56
58
|
break;
|
|
57
59
|
case '}': r = '\\right\\}';
|
|
58
60
|
break;
|
|
59
|
-
case '
|
|
61
|
+
case '\u27E9': r = ' \\right\\rangle';
|
|
60
62
|
break;
|
|
61
|
-
case '
|
|
63
|
+
case '\u230B': r = ' \\right\\rfloor';
|
|
62
64
|
break;
|
|
63
|
-
case '
|
|
65
|
+
case '\u2309': r = ' \\right\\rceil';
|
|
64
66
|
break;
|
|
65
|
-
case '
|
|
67
|
+
case '\u231D': r = ' \\right\\urcorner';
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
68
70
|
return (stretchy ? r : r.replace('\\right', ''));
|
|
@@ -98,11 +100,17 @@
|
|
|
98
100
|
function createHTMLParser () {
|
|
99
101
|
const Parser = function () {};
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
const hasDocument =
|
|
104
|
+
typeof document !== 'undefined' &&
|
|
105
|
+
document &&
|
|
106
|
+
document.implementation &&
|
|
107
|
+
typeof document.implementation.createHTMLDocument === 'function';
|
|
108
|
+
|
|
109
|
+
if (hasDocument) {
|
|
110
|
+
if (typeof process !== 'undefined' && false && shouldUseActiveX()) {
|
|
103
111
|
Parser.prototype.parseFromString = function (string) {
|
|
104
112
|
const doc = new window.ActiveXObject('htmlfile');
|
|
105
|
-
doc.designMode = 'on';
|
|
113
|
+
doc.designMode = 'on';
|
|
106
114
|
doc.open();
|
|
107
115
|
doc.write(string);
|
|
108
116
|
doc.close();
|
|
@@ -119,11 +127,9 @@
|
|
|
119
127
|
}
|
|
120
128
|
} else {
|
|
121
129
|
Parser.prototype.parseFromString = function (string) {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
doc.close();
|
|
126
|
-
return doc
|
|
130
|
+
const domino$1 = domino;
|
|
131
|
+
const window = domino$1.createWindow(string || '');
|
|
132
|
+
return window.document
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
135
|
return Parser
|
|
@@ -141,11 +147,54 @@
|
|
|
141
147
|
|
|
142
148
|
const HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
|
|
143
149
|
|
|
150
|
+
function normalizeMathMLInput(input) {
|
|
151
|
+
let s = input == null ? '' : String(input);
|
|
152
|
+
|
|
153
|
+
if (s.includes('<math') && /\\[ntr"]/.test(s)) {
|
|
154
|
+
s = s
|
|
155
|
+
.replace(/\\r\\n/g, '\n')
|
|
156
|
+
.replace(/\\n/g, '\n')
|
|
157
|
+
.replace(/\\t/g, '\t')
|
|
158
|
+
.replace(/\\"/g, '"')
|
|
159
|
+
.replace(/\\'/g, "'");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
s = s.replace(/^\s*<\?xml[\s\S]*?\?>\s*/i, '');
|
|
163
|
+
|
|
164
|
+
s = s.replace(
|
|
165
|
+
/xmlns\s*=\s*(["'])\s*`?\s*(https?:\/\/www\.w3\.org\/1998\/Math\/MathML)\s*`?\s*\1/gi,
|
|
166
|
+
'xmlns="$2"'
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
s = s.replace(/xmlns\s*=\s*(["'])\s*`?\s*http:\/\/www\.w3\.org\/1998\/Math\/MathML\\`?\s*\1/gi, 'xmlns="http://www.w3.org/1998/Math/MathML"');
|
|
170
|
+
|
|
171
|
+
if (!/<math\b/i.test(s) && /<(msub|mrow|mi|mn|mo|mfrac|msup|msubsup|munder|mover|munderover|mtable)\b/i.test(s)) {
|
|
172
|
+
s = `<math xmlns="http://www.w3.org/1998/Math/MathML">${s}</math>`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return s;
|
|
176
|
+
}
|
|
177
|
+
|
|
144
178
|
const NodeTool = {
|
|
145
179
|
parseMath: function(html) {
|
|
180
|
+
const normalized = normalizeMathMLInput(html);
|
|
146
181
|
const parser = new HTMLParser();
|
|
147
|
-
const doc = parser.parseFromString(
|
|
148
|
-
|
|
182
|
+
const doc = parser.parseFromString(normalized, 'text/html');
|
|
183
|
+
let math = doc && doc.querySelector ? doc.querySelector('math') : null;
|
|
184
|
+
|
|
185
|
+
if (!math) {
|
|
186
|
+
const match = normalized.match(/<math\b[\s\S]*?<\/math>/i);
|
|
187
|
+
if (match) {
|
|
188
|
+
const retryDoc = parser.parseFromString(match[0], 'text/html');
|
|
189
|
+
math = retryDoc && retryDoc.querySelector ? retryDoc.querySelector('math') : null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!math) {
|
|
194
|
+
throw new Error('Invalid MathML: missing <math> root element');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return math;
|
|
149
198
|
},
|
|
150
199
|
getChildren: function(node) {
|
|
151
200
|
return node.children;
|
|
@@ -619,7 +668,7 @@
|
|
|
619
668
|
.replace(/∫/g, " \\int "); // Tích phân
|
|
620
669
|
|
|
621
670
|
// Đảm bảo dấu cách sau các lệnh LaTeX quan trọng để tránh dính biến (vd: \intf -> \int f)
|
|
622
|
-
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
671
|
+
result = result.replace(/\\(int|sum|prod|cos|sin|tan|cot|lim(?!its)|log|ln)([a-zA-Z0-9])/g, "\\$1 $2");
|
|
623
672
|
|
|
624
673
|
return result;
|
|
625
674
|
}
|
|
@@ -765,18 +814,21 @@
|
|
|
765
814
|
"±": " \\pm ",
|
|
766
815
|
"×": " \\times ",
|
|
767
816
|
"÷": " \\div ",
|
|
768
|
-
"∑": "
|
|
769
|
-
"∏": "
|
|
770
|
-
"∫": "
|
|
817
|
+
"∑": "\\sum ",
|
|
818
|
+
"∏": "\\prod ",
|
|
819
|
+
"∫": "\\int ",
|
|
771
820
|
"−": "-",
|
|
772
821
|
"≠": " \\neq ",
|
|
773
822
|
">": " > ",
|
|
774
823
|
"=": " = ",
|
|
824
|
+
"(": "(",
|
|
825
|
+
")": ")",
|
|
775
826
|
",": ", ", // Dấu phẩy trong tập hợp
|
|
776
827
|
";": ";",
|
|
777
828
|
Ω: "\\Omega",
|
|
778
829
|
"|": " \\mid ", // PATCH: set-builder mid
|
|
779
830
|
π: " \\pi ", // PATCH: Greek letter
|
|
831
|
+
"...": "\\dots",
|
|
780
832
|
};
|
|
781
833
|
const res = operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
|
|
782
834
|
return res;
|
|
@@ -853,8 +905,8 @@
|
|
|
853
905
|
// Nếu đã là lệnh LaTeX thì giữ nguyên
|
|
854
906
|
if (content.startsWith("\\")) return content;
|
|
855
907
|
|
|
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)) {
|
|
908
|
+
// Bọc token chữ/số liền nhau bằng \text{...} (hóa học: Cu, OH, NaOH, ..., k times)
|
|
909
|
+
if (/^[A-Za-z][A-Za-z0-9\s]*$/.test(content)) {
|
|
858
910
|
return `\\text{${content}}`;
|
|
859
911
|
}
|
|
860
912
|
|
|
@@ -909,6 +961,22 @@
|
|
|
909
961
|
return text;
|
|
910
962
|
}
|
|
911
963
|
|
|
964
|
+
function wrapBaseForScript(base) {
|
|
965
|
+
const t = (base ?? "").trim();
|
|
966
|
+
if (!t) return "";
|
|
967
|
+
if (t.startsWith("{") && t.endsWith("}")) return t;
|
|
968
|
+
if (/^\\[a-zA-Z]+$/.test(t)) return t;
|
|
969
|
+
const needsWrap =
|
|
970
|
+
t.includes("_") ||
|
|
971
|
+
t.includes("^") ||
|
|
972
|
+
/\s/.test(t) ||
|
|
973
|
+
t.startsWith("\\left") ||
|
|
974
|
+
t.startsWith("\\right") ||
|
|
975
|
+
/\\[a-zA-Z]+/.test(t) ||
|
|
976
|
+
/[{}]/.test(t);
|
|
977
|
+
return needsWrap ? `{${t}}` : t;
|
|
978
|
+
}
|
|
979
|
+
|
|
912
980
|
function parseContainer(node, children) {
|
|
913
981
|
const render = getRender(node);
|
|
914
982
|
if (render) {
|
|
@@ -927,22 +995,6 @@
|
|
|
927
995
|
// lefts là mảng object: { op: "(", index: 5 }
|
|
928
996
|
let lefts = [];
|
|
929
997
|
|
|
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
998
|
Array.prototype.forEach.call(children, (node, idx) => {
|
|
947
999
|
// PATCH: Thin space between variables/numbers in mfrac numerator (k 2 π)
|
|
948
1000
|
if (
|
|
@@ -970,55 +1022,59 @@
|
|
|
970
1022
|
if (Brackets.contains(op)) {
|
|
971
1023
|
let stretchy = NodeTool.getAttr(node, "stretchy", "true");
|
|
972
1024
|
stretchy = ["", "true"].indexOf(stretchy) > -1;
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
|
|
1025
|
+
const parentNode = node.parentNode;
|
|
1026
|
+
const isInPower = parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
1027
|
+
if (isInPower) {
|
|
1028
|
+
stretchy = false;
|
|
977
1029
|
}
|
|
978
1030
|
|
|
979
1031
|
if (Brackets.isRight(op)) {
|
|
980
1032
|
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.
|
|
1033
|
+
if (nearLeftObj && Brackets.isPair(nearLeftObj.op, op)) {
|
|
1034
|
+
const leftIdx = nearLeftObj.index;
|
|
1035
|
+
const leftRendered =
|
|
1036
|
+
typeof leftIdx === "number" ? String(parts[leftIdx] ?? "") : "";
|
|
1037
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1038
|
+
|
|
1039
|
+
const leftRenderedTrimmed = leftRendered.trim();
|
|
1040
|
+
const rightRenderedTrimmed = String(rightRendered).trim();
|
|
1041
|
+
|
|
1042
|
+
if (
|
|
1043
|
+
leftRenderedTrimmed.startsWith("\\left") &&
|
|
1044
|
+
!rightRenderedTrimmed.startsWith("\\right")
|
|
1045
|
+
) {
|
|
1046
|
+
parts[leftIdx] = leftRendered.replace("\\left", "");
|
|
1047
|
+
} else if (
|
|
1048
|
+
!leftRenderedTrimmed.startsWith("\\left") &&
|
|
1049
|
+
rightRenderedTrimmed.startsWith("\\right")
|
|
1050
|
+
) {
|
|
1051
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
995
1052
|
}
|
|
996
|
-
|
|
1053
|
+
|
|
1054
|
+
if (rightRendered) parts.push(rightRendered);
|
|
997
1055
|
lefts.pop();
|
|
1056
|
+
} else if (Brackets.isLeft(op)) {
|
|
1057
|
+
// If it's a Right bracket but doesn't match the current Left,
|
|
1058
|
+
// AND it is also a Left bracket (e.g. '|'), treat it as a new Left.
|
|
1059
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1060
|
+
if (partToPush) parts.push(partToPush);
|
|
1061
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
998
1062
|
} else {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1063
|
+
// Unmatched right bracket
|
|
1064
|
+
let rightRendered = Brackets.parseRight(op, stretchy);
|
|
1065
|
+
rightRendered = String(rightRendered).replace("\\right", "");
|
|
1066
|
+
if (rightRendered) parts.push(rightRendered);
|
|
1003
1067
|
}
|
|
1004
1068
|
} else {
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
let partToPush = "";
|
|
1011
|
-
if (stretchy && !isInPower) {
|
|
1012
|
-
partToPush = `\\left${escapedOp}`;
|
|
1069
|
+
// Must be Left bracket (or only Left)
|
|
1070
|
+
if (Brackets.isLeft(op)) {
|
|
1071
|
+
const partToPush = Brackets.parseLeft(op, stretchy);
|
|
1072
|
+
if (partToPush) parts.push(partToPush);
|
|
1073
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
1013
1074
|
} else {
|
|
1014
|
-
|
|
1075
|
+
// Should not happen if Brackets.contains is correct
|
|
1076
|
+
if (op) parts.push(op);
|
|
1015
1077
|
}
|
|
1016
|
-
if (partToPush) {
|
|
1017
|
-
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
1018
|
-
parts.push(partToPush);
|
|
1019
|
-
}
|
|
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
1078
|
}
|
|
1023
1079
|
} else {
|
|
1024
1080
|
const parsedOperator = parseOperator(node);
|
|
@@ -1027,45 +1083,6 @@
|
|
|
1027
1083
|
parts.push(parsedOperator);
|
|
1028
1084
|
}
|
|
1029
1085
|
}
|
|
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
1086
|
} else {
|
|
1070
1087
|
// Các node khác như <mtext>, #text, v.v.
|
|
1071
1088
|
const parsed = parse(node);
|
|
@@ -1077,40 +1094,13 @@
|
|
|
1077
1094
|
});
|
|
1078
1095
|
|
|
1079
1096
|
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1080
|
-
//
|
|
1097
|
+
// để tránh sinh LaTeX không hợp lệ (\left mà không có \right)
|
|
1081
1098
|
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
1099
|
lefts.forEach((leftObj) => {
|
|
1098
1100
|
const idx = leftObj && leftObj.index;
|
|
1099
1101
|
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
|
|
1102
|
+
// Use regex with whitespace support and global flag just in case
|
|
1103
|
+
parts[idx] = String(parts[idx]).replace(/\\left/g, "");
|
|
1114
1104
|
});
|
|
1115
1105
|
}
|
|
1116
1106
|
|
|
@@ -1118,6 +1108,44 @@
|
|
|
1118
1108
|
return parts;
|
|
1119
1109
|
}
|
|
1120
1110
|
|
|
1111
|
+
function isLimitOperator(base) {
|
|
1112
|
+
const t = (base || "").trim();
|
|
1113
|
+
const ops = [
|
|
1114
|
+
"\\sum",
|
|
1115
|
+
"\\prod",
|
|
1116
|
+
"\\coprod",
|
|
1117
|
+
"\\int",
|
|
1118
|
+
"\\oint",
|
|
1119
|
+
"\\bigcap",
|
|
1120
|
+
"\\bigcup",
|
|
1121
|
+
"\\bigsqcup",
|
|
1122
|
+
"\\bigvee",
|
|
1123
|
+
"\\bigwedge",
|
|
1124
|
+
"\\bigodot",
|
|
1125
|
+
"\\bigotimes",
|
|
1126
|
+
"\\bigoplus",
|
|
1127
|
+
"\\biguplus",
|
|
1128
|
+
"\\lim",
|
|
1129
|
+
"\\max",
|
|
1130
|
+
"\\min",
|
|
1131
|
+
"\\sup",
|
|
1132
|
+
"\\inf",
|
|
1133
|
+
"\\det",
|
|
1134
|
+
"\\gcd",
|
|
1135
|
+
"\\Pr",
|
|
1136
|
+
"\\limsup",
|
|
1137
|
+
"\\liminf",
|
|
1138
|
+
];
|
|
1139
|
+
return ops.some(
|
|
1140
|
+
(op) =>
|
|
1141
|
+
t === op ||
|
|
1142
|
+
t.startsWith(op + " ") ||
|
|
1143
|
+
t.startsWith(op + "\\") ||
|
|
1144
|
+
t.startsWith(op + "^") ||
|
|
1145
|
+
t.startsWith(op + "_")
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1121
1149
|
function getRender(node) {
|
|
1122
1150
|
let render = undefined;
|
|
1123
1151
|
const nodeName = NodeTool.getNodeName(node);
|
|
@@ -1266,9 +1294,11 @@
|
|
|
1266
1294
|
NodeTool.getNodeText(sub.firstChild).trim() === "")
|
|
1267
1295
|
) {
|
|
1268
1296
|
const lastChild = sub.lastChild;
|
|
1269
|
-
return lastChild
|
|
1297
|
+
return lastChild
|
|
1298
|
+
? `${wrapBaseForScript(base)}_{${parse(lastChild)}}`
|
|
1299
|
+
: base;
|
|
1270
1300
|
}
|
|
1271
|
-
return `${base}_{${parse(sub)}}`;
|
|
1301
|
+
return `${wrapBaseForScript(base)}_{${parse(sub)}}`;
|
|
1272
1302
|
};
|
|
1273
1303
|
break;
|
|
1274
1304
|
|
|
@@ -1276,22 +1306,9 @@
|
|
|
1276
1306
|
render = function (node, children) {
|
|
1277
1307
|
const childrenArray = Array.from(children);
|
|
1278
1308
|
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
|
-
}
|
|
1309
|
+
const base = parse(childrenArray[0]) || "";
|
|
1293
1310
|
const sup = parse(childrenArray[1]) || "";
|
|
1294
|
-
return `${base}^{${sup}}`;
|
|
1311
|
+
return `${wrapBaseForScript(base)}^{${sup}}`;
|
|
1295
1312
|
};
|
|
1296
1313
|
break;
|
|
1297
1314
|
|
|
@@ -1315,7 +1332,7 @@
|
|
|
1315
1332
|
.join("");
|
|
1316
1333
|
return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
|
|
1317
1334
|
}
|
|
1318
|
-
return `${base}_{${sub}}^{${sup}}`;
|
|
1335
|
+
return `${wrapBaseForScript(base)}_{${sub}}^{${sup}}`;
|
|
1319
1336
|
};
|
|
1320
1337
|
break;
|
|
1321
1338
|
|
|
@@ -1325,9 +1342,15 @@
|
|
|
1325
1342
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1326
1343
|
const base = parse(childrenArray[0]) || "";
|
|
1327
1344
|
const over = parse(childrenArray[1]) || "";
|
|
1328
|
-
const
|
|
1345
|
+
const overNode = childrenArray[1];
|
|
1346
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1347
|
+
const overText = NodeTool.getNodeText(overNode)?.trim() || "";
|
|
1329
1348
|
const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
|
|
1330
1349
|
|
|
1350
|
+
// Handle arrows with extensible commands
|
|
1351
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow{${over}}`;
|
|
1352
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow{${over}}`;
|
|
1353
|
+
|
|
1331
1354
|
// Handle biology notation (double overline)
|
|
1332
1355
|
if (overText === "¯") {
|
|
1333
1356
|
const parentNode = node.parentNode;
|
|
@@ -1343,6 +1366,27 @@
|
|
|
1343
1366
|
|
|
1344
1367
|
if (overText === "→" && isAccent) return `\\vec{${base}}`;
|
|
1345
1368
|
if (overText === "^" && isAccent) return `\\hat{${base}}`;
|
|
1369
|
+
if (overText === "\u23DE") return `\\overbrace{${base}}`;
|
|
1370
|
+
|
|
1371
|
+
// Check for nested overbrace (layer-2)
|
|
1372
|
+
if (NodeTool.getNodeName(overNode) === "mover") {
|
|
1373
|
+
const innerChildren = Array.from(NodeTool.getChildren(overNode));
|
|
1374
|
+
if (innerChildren.length >= 2) {
|
|
1375
|
+
const innerBaseText = NodeTool.getNodeText(innerChildren[0]).trim();
|
|
1376
|
+
if (innerBaseText === "\u23DE") {
|
|
1377
|
+
const label = parse(innerChildren[1]);
|
|
1378
|
+
return `\\overbrace{${base}}\\limits^{${label}}`;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (base.startsWith("\\overbrace") || base.startsWith("\\underbrace")) {
|
|
1384
|
+
return `${base}\\limits^{${over}}`;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
if (isLimitOperator(base)) {
|
|
1388
|
+
return `${base}\\limits^{${over}}`;
|
|
1389
|
+
}
|
|
1346
1390
|
return `\\overset{${over}}{${base}}`;
|
|
1347
1391
|
};
|
|
1348
1392
|
break;
|
|
@@ -1353,10 +1397,21 @@
|
|
|
1353
1397
|
if (!childrenArray || childrenArray.length < 2) return "";
|
|
1354
1398
|
const base = parse(childrenArray[0]) || "";
|
|
1355
1399
|
const under = parse(childrenArray[1]) || "";
|
|
1400
|
+
const baseText = NodeTool.getNodeText(childrenArray[0])?.trim() || "";
|
|
1401
|
+
const underText = NodeTool.getNodeText(childrenArray[1])?.trim() || "";
|
|
1356
1402
|
const isUnderAccent =
|
|
1357
1403
|
NodeTool.getAttr(node, "accentunder", "false") === "true";
|
|
1358
1404
|
|
|
1405
|
+
// Handle arrows with extensible commands
|
|
1406
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{}`;
|
|
1407
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{}`;
|
|
1408
|
+
|
|
1359
1409
|
if (base === "∫") return `\\int_{${under}}`;
|
|
1410
|
+
if (underText === "\u23DF") return `\\underbrace{${base}}`;
|
|
1411
|
+
|
|
1412
|
+
if (isLimitOperator(base)) {
|
|
1413
|
+
return `${base}\\limits_{${under}}`;
|
|
1414
|
+
}
|
|
1360
1415
|
return `\\underset{${under}}{${base}}`;
|
|
1361
1416
|
};
|
|
1362
1417
|
break;
|
|
@@ -1370,19 +1425,19 @@
|
|
|
1370
1425
|
const over = parse(childrenArray[2]);
|
|
1371
1426
|
const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
|
|
1372
1427
|
|
|
1373
|
-
// Special handling for chemical reaction arrow
|
|
1374
|
-
if (
|
|
1375
|
-
|
|
1376
|
-
NodeTool.getNodeName(childrenArray[1]) === "msup"
|
|
1377
|
-
) {
|
|
1378
|
-
return `\\xrightarrow[${under}]{${over}}`;
|
|
1379
|
-
}
|
|
1428
|
+
// Special handling for chemical reaction arrow and other arrows
|
|
1429
|
+
if (baseText === "→" || baseText === "⟶") return `\\xrightarrow[${under}]{${over}}`;
|
|
1430
|
+
if (baseText === "←" || baseText === "⟵") return `\\xleftarrow[${under}]{${over}}`;
|
|
1380
1431
|
|
|
1381
1432
|
if (baseText === "∫") return `\\int_{${under}}^{${over}}`;
|
|
1382
1433
|
if (baseText === "∑") return `\\sum_{${under}}^{${over}}`;
|
|
1383
1434
|
if (baseText === "∏") return `\\prod_{${under}}^{${over}}`;
|
|
1384
1435
|
if (baseText === "|") return `\\big|_{${under}}^{${over}}`;
|
|
1385
|
-
|
|
1436
|
+
|
|
1437
|
+
if (isLimitOperator(base)) {
|
|
1438
|
+
return `${base}\\limits_{${under}}^{${over}}`;
|
|
1439
|
+
}
|
|
1440
|
+
return `\\underset{${under}}{\\overset{${over}}{${base}}}`;
|
|
1386
1441
|
};
|
|
1387
1442
|
break;
|
|
1388
1443
|
|
|
@@ -1390,30 +1445,56 @@
|
|
|
1390
1445
|
render = function (node, children) {
|
|
1391
1446
|
const childrenArray = Array.from(children);
|
|
1392
1447
|
if (!childrenArray || childrenArray.length < 1) return "";
|
|
1393
|
-
const base = parse(childrenArray[0]);
|
|
1394
|
-
|
|
1395
|
-
|
|
1448
|
+
const base = parse(childrenArray[0]) || "";
|
|
1449
|
+
|
|
1450
|
+
const postSub = [];
|
|
1451
|
+
const postSup = [];
|
|
1452
|
+
const preSub = [];
|
|
1453
|
+
const preSup = [];
|
|
1454
|
+
|
|
1396
1455
|
let i = 1;
|
|
1456
|
+
let inPrescripts = false;
|
|
1397
1457
|
|
|
1398
1458
|
while (i < childrenArray.length) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1459
|
+
const name = NodeTool.getNodeName(childrenArray[i]);
|
|
1460
|
+
if (name === "mprescripts") {
|
|
1461
|
+
inPrescripts = true;
|
|
1462
|
+
i += 1;
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const subNode = childrenArray[i];
|
|
1467
|
+
const supNode = childrenArray[i + 1];
|
|
1468
|
+
if (!subNode || !supNode) break;
|
|
1469
|
+
|
|
1470
|
+
const sub = parse(subNode) || "";
|
|
1471
|
+
const sup = parse(supNode) || "";
|
|
1472
|
+
|
|
1473
|
+
if (inPrescripts) {
|
|
1474
|
+
if (sub) preSub.push(sub);
|
|
1475
|
+
if (sup) preSup.push(sup);
|
|
1407
1476
|
} else {
|
|
1408
|
-
if (
|
|
1409
|
-
|
|
1410
|
-
childrenArray[i + 1]
|
|
1411
|
-
)}}`;
|
|
1412
|
-
i += 2;
|
|
1413
|
-
} else break;
|
|
1477
|
+
if (sub) postSub.push(sub);
|
|
1478
|
+
if (sup) postSup.push(sup);
|
|
1414
1479
|
}
|
|
1480
|
+
|
|
1481
|
+
i += 2;
|
|
1415
1482
|
}
|
|
1416
|
-
|
|
1483
|
+
|
|
1484
|
+
const preSubStr = preSub.join(" ");
|
|
1485
|
+
const preSupStr = preSup.join(" ");
|
|
1486
|
+
const postSubStr = postSub.join(" ");
|
|
1487
|
+
const postSupStr = postSup.join(" ");
|
|
1488
|
+
|
|
1489
|
+
let pre = "";
|
|
1490
|
+
if (preSubStr) pre += `_{${preSubStr}}`;
|
|
1491
|
+
if (preSupStr) pre += `^{${preSupStr}}`;
|
|
1492
|
+
|
|
1493
|
+
let post = "";
|
|
1494
|
+
if (postSubStr) post += `_{${postSubStr}}`;
|
|
1495
|
+
if (postSupStr) post += `^{${postSupStr}}`;
|
|
1496
|
+
|
|
1497
|
+
return `${pre}${wrapBaseForScript(base)}${post}`;
|
|
1417
1498
|
};
|
|
1418
1499
|
break;
|
|
1419
1500
|
|
|
@@ -1514,7 +1595,28 @@
|
|
|
1514
1595
|
const num = parse(childrenArray[0]);
|
|
1515
1596
|
const den = parse(childrenArray[1]);
|
|
1516
1597
|
const linethickness = NodeTool.getAttr(node, "linethickness", "medium");
|
|
1517
|
-
|
|
1598
|
+
const bevelled = NodeTool.getAttr(node, "bevelled", "false");
|
|
1599
|
+
|
|
1600
|
+
if (bevelled === "true") {
|
|
1601
|
+
return `{}^{${num}}/_{${den}}`;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
if (["0", "0px"].indexOf(linethickness) > -1) {
|
|
1605
|
+
const prevNode = NodeTool.getPrevNode(node);
|
|
1606
|
+
const nextNode = NodeTool.getNextNode(node);
|
|
1607
|
+
if (
|
|
1608
|
+
prevNode &&
|
|
1609
|
+
NodeTool.getNodeName(prevNode) === "mo" &&
|
|
1610
|
+
NodeTool.getNodeText(prevNode).trim() === "(" &&
|
|
1611
|
+
nextNode &&
|
|
1612
|
+
NodeTool.getNodeName(nextNode) === "mo" &&
|
|
1613
|
+
NodeTool.getNodeText(nextNode).trim() === ")"
|
|
1614
|
+
) {
|
|
1615
|
+
return `\\DELETE_BRACKET_L\\binom{${num}}{${den}}\\DELETE_BRACKET_R`;
|
|
1616
|
+
}
|
|
1617
|
+
return `{}_{${den}}^{${num}}`;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1518
1620
|
return `\\frac{${num}}{${den}}`;
|
|
1519
1621
|
};
|
|
1520
1622
|
break;
|
|
@@ -1524,7 +1626,10 @@
|
|
|
1524
1626
|
const childrenArray = Array.from(children);
|
|
1525
1627
|
const open = NodeTool.getAttr(node, "open", "(");
|
|
1526
1628
|
const close = NodeTool.getAttr(node, "close", ")");
|
|
1527
|
-
const
|
|
1629
|
+
const separatorsStr = NodeTool.getAttr(node, "separators", ",");
|
|
1630
|
+
const separators = separatorsStr
|
|
1631
|
+
.split("")
|
|
1632
|
+
.filter((c) => c.trim().length === 1);
|
|
1528
1633
|
|
|
1529
1634
|
// Xử lý đặc biệt cho trường hợp dấu ngoặc đơn |
|
|
1530
1635
|
if (open === "|") {
|
|
@@ -1575,9 +1680,10 @@
|
|
|
1575
1680
|
parts.push(parse(child));
|
|
1576
1681
|
if (
|
|
1577
1682
|
index < childrenArray.length - 1 &&
|
|
1578
|
-
separators
|
|
1683
|
+
separators.length > 0
|
|
1579
1684
|
) {
|
|
1580
|
-
|
|
1685
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1686
|
+
if (sep) parts.push(sep);
|
|
1581
1687
|
}
|
|
1582
1688
|
});
|
|
1583
1689
|
return `\\left[${parts.join("")}\\right)`;
|
|
@@ -1589,18 +1695,20 @@
|
|
|
1589
1695
|
parts.push(parse(child));
|
|
1590
1696
|
if (
|
|
1591
1697
|
index < childrenArray.length - 1 &&
|
|
1592
|
-
separators
|
|
1698
|
+
separators.length > 0
|
|
1593
1699
|
) {
|
|
1594
|
-
|
|
1700
|
+
const sep = separators[index] ?? separators[separators.length - 1];
|
|
1701
|
+
if (sep) parts.push(sep);
|
|
1595
1702
|
}
|
|
1596
1703
|
});
|
|
1597
1704
|
const content = parts.join("");
|
|
1598
1705
|
|
|
1599
1706
|
if (open === "{" && close === "}") return `\\{${content}\\}`;
|
|
1600
1707
|
if (open === "|" && close === "|") return `\\left|${content}\\right|`;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
return
|
|
1708
|
+
const left = open ? Brackets.parseLeft(open) : "";
|
|
1709
|
+
const right = close ? Brackets.parseRight(close) : "";
|
|
1710
|
+
if (!close && open) return `${left}${content}\\right.`;
|
|
1711
|
+
return `${left}${content}${right}`;
|
|
1604
1712
|
};
|
|
1605
1713
|
break;
|
|
1606
1714
|
|
|
@@ -1626,10 +1734,17 @@
|
|
|
1626
1734
|
case "mn":
|
|
1627
1735
|
case "mo":
|
|
1628
1736
|
case "ms":
|
|
1629
|
-
case "mtext":
|
|
1630
1737
|
render = getRender_joinSeparator("@content");
|
|
1631
1738
|
break;
|
|
1632
1739
|
|
|
1740
|
+
case "mtext":
|
|
1741
|
+
render = function (node, children) {
|
|
1742
|
+
const childrenArray = Array.from(children);
|
|
1743
|
+
const content = renderChildren(childrenArray).join("");
|
|
1744
|
+
return `\\text{${content}}`;
|
|
1745
|
+
};
|
|
1746
|
+
break;
|
|
1747
|
+
|
|
1633
1748
|
case "mphantom":
|
|
1634
1749
|
render = function (node, children) {
|
|
1635
1750
|
const childrenArray = Array.from(children);
|