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