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