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