ed-mathml2tex 0.2.0 → 0.2.2
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.
|
@@ -762,7 +762,7 @@ function parseOperator(node) {
|
|
|
762
762
|
"÷": " \\div ",
|
|
763
763
|
"∑": " \\sum ",
|
|
764
764
|
"∏": " \\prod ",
|
|
765
|
-
"∫": "
|
|
765
|
+
"∫": "\\int",
|
|
766
766
|
"−": "-",
|
|
767
767
|
"≠": " \\neq ",
|
|
768
768
|
">": " > ",
|
|
@@ -812,7 +812,7 @@ function parseElementMs(node) {
|
|
|
812
812
|
|
|
813
813
|
// Math Text
|
|
814
814
|
function parseElementMtext(node) {
|
|
815
|
-
let content = NodeTool.getNodeText(node)
|
|
815
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
816
816
|
.replace(/\s*=\s*/g, " = ")
|
|
817
817
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
818
818
|
.trim();
|
|
@@ -866,6 +866,14 @@ function parseElementMspace(node) {
|
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
function escapeSpecialChars(text) {
|
|
869
|
+
// Strip problematic Unicode characters:
|
|
870
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
871
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
872
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
873
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
874
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
875
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
876
|
+
|
|
869
877
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
870
878
|
if (
|
|
871
879
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -896,7 +904,8 @@ function parseContainer(node, children) {
|
|
|
896
904
|
|
|
897
905
|
function renderChildren(children) {
|
|
898
906
|
const parts = [];
|
|
899
|
-
|
|
907
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
908
|
+
let lefts = [];
|
|
900
909
|
|
|
901
910
|
if (
|
|
902
911
|
children.length >= 3 &&
|
|
@@ -948,8 +957,8 @@ function renderChildren(children) {
|
|
|
948
957
|
}
|
|
949
958
|
|
|
950
959
|
if (Brackets.isRight(op)) {
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
960
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
961
|
+
if (nearLeftObj) {
|
|
953
962
|
const parentNode = node.parentNode;
|
|
954
963
|
const isInPower =
|
|
955
964
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -964,6 +973,7 @@ function renderChildren(children) {
|
|
|
964
973
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
965
974
|
parts.push(partToPush);
|
|
966
975
|
}
|
|
976
|
+
// matched a left: remove corresponding left object
|
|
967
977
|
lefts.pop();
|
|
968
978
|
} else {
|
|
969
979
|
if (escapedOp) {
|
|
@@ -987,7 +997,8 @@ function renderChildren(children) {
|
|
|
987
997
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
988
998
|
parts.push(partToPush);
|
|
989
999
|
}
|
|
990
|
-
|
|
1000
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1001
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
991
1002
|
}
|
|
992
1003
|
} else {
|
|
993
1004
|
const parsedOperator = parseOperator(node);
|
|
@@ -1022,8 +1033,8 @@ function renderChildren(children) {
|
|
|
1022
1033
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1023
1034
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1024
1035
|
|
|
1025
|
-
const
|
|
1026
|
-
if (
|
|
1036
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1037
|
+
if (nearLeftObj) {
|
|
1027
1038
|
lefts.pop();
|
|
1028
1039
|
}
|
|
1029
1040
|
} else {
|
|
@@ -1044,6 +1055,45 @@ function renderChildren(children) {
|
|
|
1044
1055
|
}
|
|
1045
1056
|
}
|
|
1046
1057
|
});
|
|
1058
|
+
|
|
1059
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1060
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1061
|
+
if (lefts && lefts.length > 0) {
|
|
1062
|
+
const rightForLeft = (left) => {
|
|
1063
|
+
switch (left) {
|
|
1064
|
+
case "(":
|
|
1065
|
+
return ")";
|
|
1066
|
+
case "[":
|
|
1067
|
+
return "]";
|
|
1068
|
+
case "{":
|
|
1069
|
+
return "}";
|
|
1070
|
+
case "|":
|
|
1071
|
+
return "|";
|
|
1072
|
+
default:
|
|
1073
|
+
return ".";
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
lefts.forEach((leftObj) => {
|
|
1078
|
+
const idx = leftObj && leftObj.index;
|
|
1079
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1080
|
+
|
|
1081
|
+
const left = leftObj.op;
|
|
1082
|
+
const right = rightForLeft(left);
|
|
1083
|
+
|
|
1084
|
+
// 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
|
|
1085
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1086
|
+
const hasMatchingRight =
|
|
1087
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1088
|
+
|
|
1089
|
+
if (!hasMatchingRight) {
|
|
1090
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1091
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1092
|
+
}
|
|
1093
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1047
1097
|
lefts = undefined;
|
|
1048
1098
|
return parts;
|
|
1049
1099
|
}
|
|
@@ -760,7 +760,7 @@ function parseOperator(node) {
|
|
|
760
760
|
"÷": " \\div ",
|
|
761
761
|
"∑": " \\sum ",
|
|
762
762
|
"∏": " \\prod ",
|
|
763
|
-
"∫": "
|
|
763
|
+
"∫": "\\int",
|
|
764
764
|
"−": "-",
|
|
765
765
|
"≠": " \\neq ",
|
|
766
766
|
">": " > ",
|
|
@@ -810,7 +810,7 @@ function parseElementMs(node) {
|
|
|
810
810
|
|
|
811
811
|
// Math Text
|
|
812
812
|
function parseElementMtext(node) {
|
|
813
|
-
let content = NodeTool.getNodeText(node)
|
|
813
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
814
814
|
.replace(/\s*=\s*/g, " = ")
|
|
815
815
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
816
816
|
.trim();
|
|
@@ -864,6 +864,14 @@ function parseElementMspace(node) {
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
function escapeSpecialChars(text) {
|
|
867
|
+
// Strip problematic Unicode characters:
|
|
868
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
869
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
870
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
871
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
872
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
873
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
874
|
+
|
|
867
875
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
868
876
|
if (
|
|
869
877
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -894,7 +902,8 @@ function parseContainer(node, children) {
|
|
|
894
902
|
|
|
895
903
|
function renderChildren(children) {
|
|
896
904
|
const parts = [];
|
|
897
|
-
|
|
905
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
906
|
+
let lefts = [];
|
|
898
907
|
|
|
899
908
|
if (
|
|
900
909
|
children.length >= 3 &&
|
|
@@ -946,8 +955,8 @@ function renderChildren(children) {
|
|
|
946
955
|
}
|
|
947
956
|
|
|
948
957
|
if (Brackets.isRight(op)) {
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
958
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
959
|
+
if (nearLeftObj) {
|
|
951
960
|
const parentNode = node.parentNode;
|
|
952
961
|
const isInPower =
|
|
953
962
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -962,6 +971,7 @@ function renderChildren(children) {
|
|
|
962
971
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
963
972
|
parts.push(partToPush);
|
|
964
973
|
}
|
|
974
|
+
// matched a left: remove corresponding left object
|
|
965
975
|
lefts.pop();
|
|
966
976
|
} else {
|
|
967
977
|
if (escapedOp) {
|
|
@@ -985,7 +995,8 @@ function renderChildren(children) {
|
|
|
985
995
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
986
996
|
parts.push(partToPush);
|
|
987
997
|
}
|
|
988
|
-
|
|
998
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
999
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
989
1000
|
}
|
|
990
1001
|
} else {
|
|
991
1002
|
const parsedOperator = parseOperator(node);
|
|
@@ -1020,8 +1031,8 @@ function renderChildren(children) {
|
|
|
1020
1031
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1021
1032
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1022
1033
|
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1034
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1035
|
+
if (nearLeftObj) {
|
|
1025
1036
|
lefts.pop();
|
|
1026
1037
|
}
|
|
1027
1038
|
} else {
|
|
@@ -1042,6 +1053,45 @@ function renderChildren(children) {
|
|
|
1042
1053
|
}
|
|
1043
1054
|
}
|
|
1044
1055
|
});
|
|
1056
|
+
|
|
1057
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1058
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1059
|
+
if (lefts && lefts.length > 0) {
|
|
1060
|
+
const rightForLeft = (left) => {
|
|
1061
|
+
switch (left) {
|
|
1062
|
+
case "(":
|
|
1063
|
+
return ")";
|
|
1064
|
+
case "[":
|
|
1065
|
+
return "]";
|
|
1066
|
+
case "{":
|
|
1067
|
+
return "}";
|
|
1068
|
+
case "|":
|
|
1069
|
+
return "|";
|
|
1070
|
+
default:
|
|
1071
|
+
return ".";
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
lefts.forEach((leftObj) => {
|
|
1076
|
+
const idx = leftObj && leftObj.index;
|
|
1077
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1078
|
+
|
|
1079
|
+
const left = leftObj.op;
|
|
1080
|
+
const right = rightForLeft(left);
|
|
1081
|
+
|
|
1082
|
+
// 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
|
|
1083
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1084
|
+
const hasMatchingRight =
|
|
1085
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1086
|
+
|
|
1087
|
+
if (!hasMatchingRight) {
|
|
1088
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1089
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1090
|
+
}
|
|
1091
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1045
1095
|
lefts = undefined;
|
|
1046
1096
|
return parts;
|
|
1047
1097
|
}
|
|
@@ -766,7 +766,7 @@
|
|
|
766
766
|
"÷": " \\div ",
|
|
767
767
|
"∑": " \\sum ",
|
|
768
768
|
"∏": " \\prod ",
|
|
769
|
-
"∫": "
|
|
769
|
+
"∫": "\\int",
|
|
770
770
|
"−": "-",
|
|
771
771
|
"≠": " \\neq ",
|
|
772
772
|
">": " > ",
|
|
@@ -816,7 +816,7 @@
|
|
|
816
816
|
|
|
817
817
|
// Math Text
|
|
818
818
|
function parseElementMtext(node) {
|
|
819
|
-
let content = NodeTool.getNodeText(node)
|
|
819
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
820
820
|
.replace(/\s*=\s*/g, " = ")
|
|
821
821
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
822
822
|
.trim();
|
|
@@ -870,6 +870,14 @@
|
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
function escapeSpecialChars(text) {
|
|
873
|
+
// Strip problematic Unicode characters:
|
|
874
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
875
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
876
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
877
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
878
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
879
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
880
|
+
|
|
873
881
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
874
882
|
if (
|
|
875
883
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -900,7 +908,8 @@
|
|
|
900
908
|
|
|
901
909
|
function renderChildren(children) {
|
|
902
910
|
const parts = [];
|
|
903
|
-
|
|
911
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
912
|
+
let lefts = [];
|
|
904
913
|
|
|
905
914
|
if (
|
|
906
915
|
children.length >= 3 &&
|
|
@@ -952,8 +961,8 @@
|
|
|
952
961
|
}
|
|
953
962
|
|
|
954
963
|
if (Brackets.isRight(op)) {
|
|
955
|
-
const
|
|
956
|
-
if (
|
|
964
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
965
|
+
if (nearLeftObj) {
|
|
957
966
|
const parentNode = node.parentNode;
|
|
958
967
|
const isInPower =
|
|
959
968
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -968,6 +977,7 @@
|
|
|
968
977
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
969
978
|
parts.push(partToPush);
|
|
970
979
|
}
|
|
980
|
+
// matched a left: remove corresponding left object
|
|
971
981
|
lefts.pop();
|
|
972
982
|
} else {
|
|
973
983
|
if (escapedOp) {
|
|
@@ -991,7 +1001,8 @@
|
|
|
991
1001
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
992
1002
|
parts.push(partToPush);
|
|
993
1003
|
}
|
|
994
|
-
|
|
1004
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1005
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
995
1006
|
}
|
|
996
1007
|
} else {
|
|
997
1008
|
const parsedOperator = parseOperator(node);
|
|
@@ -1026,8 +1037,8 @@
|
|
|
1026
1037
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1027
1038
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1028
1039
|
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1040
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1041
|
+
if (nearLeftObj) {
|
|
1031
1042
|
lefts.pop();
|
|
1032
1043
|
}
|
|
1033
1044
|
} else {
|
|
@@ -1048,6 +1059,45 @@
|
|
|
1048
1059
|
}
|
|
1049
1060
|
}
|
|
1050
1061
|
});
|
|
1062
|
+
|
|
1063
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1064
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1065
|
+
if (lefts && lefts.length > 0) {
|
|
1066
|
+
const rightForLeft = (left) => {
|
|
1067
|
+
switch (left) {
|
|
1068
|
+
case "(":
|
|
1069
|
+
return ")";
|
|
1070
|
+
case "[":
|
|
1071
|
+
return "]";
|
|
1072
|
+
case "{":
|
|
1073
|
+
return "}";
|
|
1074
|
+
case "|":
|
|
1075
|
+
return "|";
|
|
1076
|
+
default:
|
|
1077
|
+
return ".";
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
lefts.forEach((leftObj) => {
|
|
1082
|
+
const idx = leftObj && leftObj.index;
|
|
1083
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1084
|
+
|
|
1085
|
+
const left = leftObj.op;
|
|
1086
|
+
const right = rightForLeft(left);
|
|
1087
|
+
|
|
1088
|
+
// 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
|
|
1089
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1090
|
+
const hasMatchingRight =
|
|
1091
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1092
|
+
|
|
1093
|
+
if (!hasMatchingRight) {
|
|
1094
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1095
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1096
|
+
}
|
|
1097
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1051
1101
|
lefts = undefined;
|
|
1052
1102
|
return parts;
|
|
1053
1103
|
}
|
package/lib/mathml2latex.cjs.js
CHANGED
|
@@ -762,7 +762,7 @@ function parseOperator(node) {
|
|
|
762
762
|
"÷": " \\div ",
|
|
763
763
|
"∑": " \\sum ",
|
|
764
764
|
"∏": " \\prod ",
|
|
765
|
-
"∫": "
|
|
765
|
+
"∫": "\\int",
|
|
766
766
|
"−": "-",
|
|
767
767
|
"≠": " \\neq ",
|
|
768
768
|
">": " > ",
|
|
@@ -812,7 +812,7 @@ function parseElementMs(node) {
|
|
|
812
812
|
|
|
813
813
|
// Math Text
|
|
814
814
|
function parseElementMtext(node) {
|
|
815
|
-
let content = NodeTool.getNodeText(node)
|
|
815
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
816
816
|
.replace(/\s*=\s*/g, " = ")
|
|
817
817
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
818
818
|
.trim();
|
|
@@ -866,6 +866,14 @@ function parseElementMspace(node) {
|
|
|
866
866
|
}
|
|
867
867
|
|
|
868
868
|
function escapeSpecialChars(text) {
|
|
869
|
+
// Strip problematic Unicode characters:
|
|
870
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
871
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
872
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
873
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
874
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
875
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
876
|
+
|
|
869
877
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
870
878
|
if (
|
|
871
879
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -896,7 +904,8 @@ function parseContainer(node, children) {
|
|
|
896
904
|
|
|
897
905
|
function renderChildren(children) {
|
|
898
906
|
const parts = [];
|
|
899
|
-
|
|
907
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
908
|
+
let lefts = [];
|
|
900
909
|
|
|
901
910
|
if (
|
|
902
911
|
children.length >= 3 &&
|
|
@@ -948,8 +957,8 @@ function renderChildren(children) {
|
|
|
948
957
|
}
|
|
949
958
|
|
|
950
959
|
if (Brackets.isRight(op)) {
|
|
951
|
-
const
|
|
952
|
-
if (
|
|
960
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
961
|
+
if (nearLeftObj) {
|
|
953
962
|
const parentNode = node.parentNode;
|
|
954
963
|
const isInPower =
|
|
955
964
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -964,6 +973,7 @@ function renderChildren(children) {
|
|
|
964
973
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
965
974
|
parts.push(partToPush);
|
|
966
975
|
}
|
|
976
|
+
// matched a left: remove corresponding left object
|
|
967
977
|
lefts.pop();
|
|
968
978
|
} else {
|
|
969
979
|
if (escapedOp) {
|
|
@@ -987,7 +997,8 @@ function renderChildren(children) {
|
|
|
987
997
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
988
998
|
parts.push(partToPush);
|
|
989
999
|
}
|
|
990
|
-
|
|
1000
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1001
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
991
1002
|
}
|
|
992
1003
|
} else {
|
|
993
1004
|
const parsedOperator = parseOperator(node);
|
|
@@ -1022,8 +1033,8 @@ function renderChildren(children) {
|
|
|
1022
1033
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1023
1034
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1024
1035
|
|
|
1025
|
-
const
|
|
1026
|
-
if (
|
|
1036
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1037
|
+
if (nearLeftObj) {
|
|
1027
1038
|
lefts.pop();
|
|
1028
1039
|
}
|
|
1029
1040
|
} else {
|
|
@@ -1044,6 +1055,45 @@ function renderChildren(children) {
|
|
|
1044
1055
|
}
|
|
1045
1056
|
}
|
|
1046
1057
|
});
|
|
1058
|
+
|
|
1059
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1060
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1061
|
+
if (lefts && lefts.length > 0) {
|
|
1062
|
+
const rightForLeft = (left) => {
|
|
1063
|
+
switch (left) {
|
|
1064
|
+
case "(":
|
|
1065
|
+
return ")";
|
|
1066
|
+
case "[":
|
|
1067
|
+
return "]";
|
|
1068
|
+
case "{":
|
|
1069
|
+
return "}";
|
|
1070
|
+
case "|":
|
|
1071
|
+
return "|";
|
|
1072
|
+
default:
|
|
1073
|
+
return ".";
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
lefts.forEach((leftObj) => {
|
|
1078
|
+
const idx = leftObj && leftObj.index;
|
|
1079
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1080
|
+
|
|
1081
|
+
const left = leftObj.op;
|
|
1082
|
+
const right = rightForLeft(left);
|
|
1083
|
+
|
|
1084
|
+
// 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
|
|
1085
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1086
|
+
const hasMatchingRight =
|
|
1087
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1088
|
+
|
|
1089
|
+
if (!hasMatchingRight) {
|
|
1090
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1091
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1092
|
+
}
|
|
1093
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1047
1097
|
lefts = undefined;
|
|
1048
1098
|
return parts;
|
|
1049
1099
|
}
|
package/lib/mathml2latex.es.js
CHANGED
|
@@ -760,7 +760,7 @@ function parseOperator(node) {
|
|
|
760
760
|
"÷": " \\div ",
|
|
761
761
|
"∑": " \\sum ",
|
|
762
762
|
"∏": " \\prod ",
|
|
763
|
-
"∫": "
|
|
763
|
+
"∫": "\\int",
|
|
764
764
|
"−": "-",
|
|
765
765
|
"≠": " \\neq ",
|
|
766
766
|
">": " > ",
|
|
@@ -810,7 +810,7 @@ function parseElementMs(node) {
|
|
|
810
810
|
|
|
811
811
|
// Math Text
|
|
812
812
|
function parseElementMtext(node) {
|
|
813
|
-
let content = NodeTool.getNodeText(node)
|
|
813
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
814
814
|
.replace(/\s*=\s*/g, " = ")
|
|
815
815
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
816
816
|
.trim();
|
|
@@ -864,6 +864,14 @@ function parseElementMspace(node) {
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
function escapeSpecialChars(text) {
|
|
867
|
+
// Strip problematic Unicode characters:
|
|
868
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
869
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
870
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
871
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
872
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
873
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
874
|
+
|
|
867
875
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
868
876
|
if (
|
|
869
877
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -894,7 +902,8 @@ function parseContainer(node, children) {
|
|
|
894
902
|
|
|
895
903
|
function renderChildren(children) {
|
|
896
904
|
const parts = [];
|
|
897
|
-
|
|
905
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
906
|
+
let lefts = [];
|
|
898
907
|
|
|
899
908
|
if (
|
|
900
909
|
children.length >= 3 &&
|
|
@@ -946,8 +955,8 @@ function renderChildren(children) {
|
|
|
946
955
|
}
|
|
947
956
|
|
|
948
957
|
if (Brackets.isRight(op)) {
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
958
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
959
|
+
if (nearLeftObj) {
|
|
951
960
|
const parentNode = node.parentNode;
|
|
952
961
|
const isInPower =
|
|
953
962
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -962,6 +971,7 @@ function renderChildren(children) {
|
|
|
962
971
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
963
972
|
parts.push(partToPush);
|
|
964
973
|
}
|
|
974
|
+
// matched a left: remove corresponding left object
|
|
965
975
|
lefts.pop();
|
|
966
976
|
} else {
|
|
967
977
|
if (escapedOp) {
|
|
@@ -985,7 +995,8 @@ function renderChildren(children) {
|
|
|
985
995
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
986
996
|
parts.push(partToPush);
|
|
987
997
|
}
|
|
988
|
-
|
|
998
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
999
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
989
1000
|
}
|
|
990
1001
|
} else {
|
|
991
1002
|
const parsedOperator = parseOperator(node);
|
|
@@ -1020,8 +1031,8 @@ function renderChildren(children) {
|
|
|
1020
1031
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1021
1032
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1022
1033
|
|
|
1023
|
-
const
|
|
1024
|
-
if (
|
|
1034
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1035
|
+
if (nearLeftObj) {
|
|
1025
1036
|
lefts.pop();
|
|
1026
1037
|
}
|
|
1027
1038
|
} else {
|
|
@@ -1042,6 +1053,45 @@ function renderChildren(children) {
|
|
|
1042
1053
|
}
|
|
1043
1054
|
}
|
|
1044
1055
|
});
|
|
1056
|
+
|
|
1057
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1058
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1059
|
+
if (lefts && lefts.length > 0) {
|
|
1060
|
+
const rightForLeft = (left) => {
|
|
1061
|
+
switch (left) {
|
|
1062
|
+
case "(":
|
|
1063
|
+
return ")";
|
|
1064
|
+
case "[":
|
|
1065
|
+
return "]";
|
|
1066
|
+
case "{":
|
|
1067
|
+
return "}";
|
|
1068
|
+
case "|":
|
|
1069
|
+
return "|";
|
|
1070
|
+
default:
|
|
1071
|
+
return ".";
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
lefts.forEach((leftObj) => {
|
|
1076
|
+
const idx = leftObj && leftObj.index;
|
|
1077
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1078
|
+
|
|
1079
|
+
const left = leftObj.op;
|
|
1080
|
+
const right = rightForLeft(left);
|
|
1081
|
+
|
|
1082
|
+
// 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
|
|
1083
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1084
|
+
const hasMatchingRight =
|
|
1085
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1086
|
+
|
|
1087
|
+
if (!hasMatchingRight) {
|
|
1088
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1089
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1090
|
+
}
|
|
1091
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1045
1095
|
lefts = undefined;
|
|
1046
1096
|
return parts;
|
|
1047
1097
|
}
|
package/lib/mathml2latex.umd.js
CHANGED
|
@@ -766,7 +766,7 @@
|
|
|
766
766
|
"÷": " \\div ",
|
|
767
767
|
"∑": " \\sum ",
|
|
768
768
|
"∏": " \\prod ",
|
|
769
|
-
"∫": "
|
|
769
|
+
"∫": "\\int",
|
|
770
770
|
"−": "-",
|
|
771
771
|
"≠": " \\neq ",
|
|
772
772
|
">": " > ",
|
|
@@ -816,7 +816,7 @@
|
|
|
816
816
|
|
|
817
817
|
// Math Text
|
|
818
818
|
function parseElementMtext(node) {
|
|
819
|
-
let content = NodeTool.getNodeText(node)
|
|
819
|
+
let content = escapeSpecialChars(NodeTool.getNodeText(node))
|
|
820
820
|
.replace(/\s*=\s*/g, " = ")
|
|
821
821
|
.replace(/\s*\.\s*/g, " \\cdot ")
|
|
822
822
|
.trim();
|
|
@@ -870,6 +870,14 @@
|
|
|
870
870
|
}
|
|
871
871
|
|
|
872
872
|
function escapeSpecialChars(text) {
|
|
873
|
+
// Strip problematic Unicode characters:
|
|
874
|
+
// - Control characters: \u0000-\u0008, \u000B-\u000C, \u000E-\u001F, \u007F-\u009F
|
|
875
|
+
// - Zero-width & Invisible: \u200B-\u200F, \u202A-\u202E, \u2060-\u206F, \uFEFF
|
|
876
|
+
// - Variation Selectors: \uFE00-\uFE0F
|
|
877
|
+
// - Private Use Area (PUA): \uE000-\uF8FF
|
|
878
|
+
// - Non-characters: \uFDD0-\uFDEF
|
|
879
|
+
text = text.replace(/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFE00-\uFE0F\uFEFF\uE000-\uF8FF\uFDD0-\uFDEF]/g, "");
|
|
880
|
+
|
|
873
881
|
// Don't escape pi, Greek, or just a-z0-9 or Unicode Greek, or empty
|
|
874
882
|
if (
|
|
875
883
|
/^\\?[a-zA-Z0-9]+$/.test(text) ||
|
|
@@ -900,7 +908,8 @@
|
|
|
900
908
|
|
|
901
909
|
function renderChildren(children) {
|
|
902
910
|
const parts = [];
|
|
903
|
-
|
|
911
|
+
// lefts là mảng object: { op: "(", index: 5 }
|
|
912
|
+
let lefts = [];
|
|
904
913
|
|
|
905
914
|
if (
|
|
906
915
|
children.length >= 3 &&
|
|
@@ -952,8 +961,8 @@
|
|
|
952
961
|
}
|
|
953
962
|
|
|
954
963
|
if (Brackets.isRight(op)) {
|
|
955
|
-
const
|
|
956
|
-
if (
|
|
964
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
965
|
+
if (nearLeftObj) {
|
|
957
966
|
const parentNode = node.parentNode;
|
|
958
967
|
const isInPower =
|
|
959
968
|
parentNode && NodeTool.getNodeName(parentNode) === "msup";
|
|
@@ -968,6 +977,7 @@
|
|
|
968
977
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
969
978
|
parts.push(partToPush);
|
|
970
979
|
}
|
|
980
|
+
// matched a left: remove corresponding left object
|
|
971
981
|
lefts.pop();
|
|
972
982
|
} else {
|
|
973
983
|
if (escapedOp) {
|
|
@@ -991,7 +1001,8 @@
|
|
|
991
1001
|
// CHỈ PUSH NẾU KHÔNG RỖNG
|
|
992
1002
|
parts.push(partToPush);
|
|
993
1003
|
}
|
|
994
|
-
|
|
1004
|
+
// Lưu vị trí token left vừa push để có thể xử lý nếu không có right
|
|
1005
|
+
lefts.push({ op: op, index: parts.length - 1 });
|
|
995
1006
|
}
|
|
996
1007
|
} else {
|
|
997
1008
|
const parsedOperator = parseOperator(node);
|
|
@@ -1026,8 +1037,8 @@
|
|
|
1026
1037
|
parts.push(`${lastPart}_{${sub}}`); // Push "\text{OH}_{2}"
|
|
1027
1038
|
// Mảng 'parts' lúc này là: ["\text{Cu}", "\text{OH}_{2}"]
|
|
1028
1039
|
|
|
1029
|
-
const
|
|
1030
|
-
if (
|
|
1040
|
+
const nearLeftObj = lefts[lefts.length - 1];
|
|
1041
|
+
if (nearLeftObj) {
|
|
1031
1042
|
lefts.pop();
|
|
1032
1043
|
}
|
|
1033
1044
|
} else {
|
|
@@ -1048,6 +1059,45 @@
|
|
|
1048
1059
|
}
|
|
1049
1060
|
}
|
|
1050
1061
|
});
|
|
1062
|
+
|
|
1063
|
+
// Nếu còn lefts (ngoặc trái) chưa được đóng, chỉ chuyển '\leftX' thành 'X'
|
|
1064
|
+
// khi không có đóng tương ứng phía sau — nếu có \right (hoặc dấu đóng) thì giữ \left
|
|
1065
|
+
if (lefts && lefts.length > 0) {
|
|
1066
|
+
const rightForLeft = (left) => {
|
|
1067
|
+
switch (left) {
|
|
1068
|
+
case "(":
|
|
1069
|
+
return ")";
|
|
1070
|
+
case "[":
|
|
1071
|
+
return "]";
|
|
1072
|
+
case "{":
|
|
1073
|
+
return "}";
|
|
1074
|
+
case "|":
|
|
1075
|
+
return "|";
|
|
1076
|
+
default:
|
|
1077
|
+
return ".";
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
lefts.forEach((leftObj) => {
|
|
1082
|
+
const idx = leftObj && leftObj.index;
|
|
1083
|
+
if (typeof idx !== "number" || !parts[idx]) return;
|
|
1084
|
+
|
|
1085
|
+
const left = leftObj.op;
|
|
1086
|
+
const right = rightForLeft(left);
|
|
1087
|
+
|
|
1088
|
+
// 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
|
|
1089
|
+
const tail = parts.slice(idx + 1).map(String).join(" ");
|
|
1090
|
+
const hasMatchingRight =
|
|
1091
|
+
(/\\right/.test(tail)) || (right !== "." && tail.indexOf(right) !== -1);
|
|
1092
|
+
|
|
1093
|
+
if (!hasMatchingRight) {
|
|
1094
|
+
// Không có đóng tương ứng: bỏ prefix '\left' để chỉ còn ký tự mở bình thường
|
|
1095
|
+
parts[idx] = String(parts[idx]).replace(/^\\left/, "");
|
|
1096
|
+
}
|
|
1097
|
+
// Nếu có đóng tương ứng thì giữ nguyên '\left...' để không sinh \right thừa
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1051
1101
|
lefts = undefined;
|
|
1052
1102
|
return parts;
|
|
1053
1103
|
}
|