ed-mathml2tex 0.0.2 → 0.0.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.
@@ -580,22 +580,47 @@ T.createMarker = function() {
580
580
  }
581
581
  };
582
582
 
583
+ function getRender_default(template) {
584
+ return function(node, children) {
585
+ const parts = renderChildren(children);
586
+ return renderTemplate(template, parts);
587
+ };
588
+ }
589
+
590
+ function getRender_joinSeparator(template, separator = '') {
591
+ return function(node, children) {
592
+ const parts = renderChildren(children);
593
+ return template.replace('@content', parts.join(separator));
594
+ };
595
+ }
596
+
597
+ function getRender_joinSeparators(template, separators) {
598
+ return function(node, children) {
599
+ const parts = renderChildren(children);
600
+ let content = '';
601
+ if (separators.length === 0) {
602
+ content = parts.join('');
603
+ } else {
604
+ content = parts.reduce((accumulator, part, index) => {
605
+ accumulator += part;
606
+ if (index < parts.length - 1) {
607
+ accumulator += separators[index] || separators[separators.length - 1];
608
+ }
609
+ return accumulator;
610
+ }, '');
611
+ }
612
+ return template.replace('@content', content);
613
+ };
614
+ }
615
+
583
616
  function convert(mathmlHtml){
584
617
  const math = NodeTool.parseMath(mathmlHtml);
585
-
586
- // Debug input
587
- console.log("Converting MathML:", mathmlHtml);
588
-
589
618
  let result = toLatex(parse(math));
590
619
 
591
620
  // Last-chance post-processing for specific patterns
592
621
  if (mathmlHtml.includes("<munder>") &&
593
622
  mathmlHtml.includes("<mo>→</mo>") &&
594
623
  mathmlHtml.includes("<mrow/>")) {
595
-
596
- console.log("Found specific pattern, forcing correct output");
597
-
598
- // Look for arrow with limits in the result
599
624
  if (result.includes("\\limits")) {
600
625
  result = "\\underset{}{\\rightarrow}";
601
626
  }
@@ -626,6 +651,18 @@ function toLatex(result) {
626
651
  result = result.replace(/→\\limits_\{([^}]*)\}\^\{([^}]*)\}/g, "\\overset{$2}{\\underset{$1}{\\rightarrow}}");
627
652
  result = result.replace(/\\rightarrow\\limits_\{([^}]*)\}\^\{([^}]*)\}/g, "\\overset{$2}{\\underset{$1}{\\rightarrow}}");
628
653
 
654
+ // Case 4: mover - fix expressions with arrow superscript
655
+ // Simple expression with arrow superscript: expr^{\rightarrow} → \overrightarrow{expr}
656
+ result = result.replace(/([^{}\s]+)\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
657
+ result = result.replace(/\{([^{}]+)\}\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
658
+
659
+ // Complex expressions with subscripts and arrow: expr_{sub}^{\rightarrow} → \overrightarrow{expr_{sub}}
660
+ result = result.replace(/([A-Za-z0-9]+)_\{([^{}]+)\}\^\{\\rightarrow\}/g, "\\overrightarrow{$1_{$2}}");
661
+ result = result.replace(/([A-Za-z0-9]+)_([0-9])\^\{\\rightarrow\}/g, "\\overrightarrow{$1_$2}");
662
+
663
+ // Very complex expressions: (expr)^{\rightarrow} → \overrightarrow{(expr)}
664
+ result = result.replace(/(\([^()]+\))\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
665
+
629
666
  // Also match if there are spaces
630
667
  result = result.replace(/→\s*\\limits\s*_\s*{\s*}/g, "\\underset{}{\\rightarrow}");
631
668
  result = result.replace(/\\rightarrow\s*\\limits\s*_\s*{\s*}/g, "\\underset{}{\\rightarrow}");
@@ -694,14 +731,27 @@ function parseOperator(node) {
694
731
  }
695
732
 
696
733
  // Math identifier
697
- function parseElementMi(node){
734
+ function parseElementMi(node) {
698
735
  let it = NodeTool.getNodeText(node).trim();
736
+
737
+ // Handle vectors (e.g. AB', AI)
738
+ if (it.includes("'")) {
739
+ return it; // Return as is to handle in mrow
740
+ }
741
+
742
+ // Handle subscripts (e.g. n₂)
743
+ if (it.match(/[a-zA-Z]\d/)) {
744
+ const base = it[0];
745
+ const sub = it[1];
746
+ return `${base}_{${sub}}`;
747
+ }
748
+
699
749
  it = MathSymbol.parseIdentifier(it);
700
750
  return escapeSpecialChars(it);
701
751
  }
702
752
 
703
753
  // Math Number
704
- function parseElementMn(node){
754
+ function parseElementMn(node) {
705
755
  let it = NodeTool.getNodeText(node).trim();
706
756
  return escapeSpecialChars(it);
707
757
  }
@@ -756,7 +806,6 @@ function renderChildren(children) {
756
806
  if(Brackets.contains(op)){
757
807
  let stretchy = NodeTool.getAttr(node, 'stretchy', 'true');
758
808
  stretchy = ['', 'true'].indexOf(stretchy) > -1;
759
- // 操作符是括號
760
809
  if(Brackets.isRight(op)){
761
810
  const nearLeft = lefts[lefts.length - 1];
762
811
  if(nearLeft){
@@ -764,21 +813,15 @@ function renderChildren(children) {
764
813
  parts.push(Brackets.parseRight(op, stretchy));
765
814
  lefts.pop();
766
815
  } else {
767
- // some brackets left side is same as right side.
768
816
  if(Brackets.isLeft(op)) {
769
817
  parts.push(Brackets.parseLeft(op, stretchy));
770
818
  lefts.push(op);
771
- } else {
772
- console.error("bracket not match");
773
819
  }
774
820
  }
775
821
  }else {
776
- // some brackets left side is same as right side.
777
822
  if(Brackets.isLeft(op)) {
778
823
  parts.push(Brackets.parseLeft(op, stretchy));
779
824
  lefts.push(op);
780
- }else {
781
- console.error("bracket not match");
782
825
  }
783
826
  }
784
827
  } else {
@@ -792,7 +835,6 @@ function renderChildren(children) {
792
835
  parts.push(parse(node));
793
836
  }
794
837
  });
795
- // 這裏非常不嚴謹
796
838
  if(lefts.length > 0){
797
839
  for(let i=0; i < lefts.length; i++){
798
840
  parts.push("\\right.");
@@ -901,12 +943,29 @@ function renderMfrac(node, children){
901
943
  return render(node, children);
902
944
  }
903
945
 
904
- function renderMfenced(node, children){
946
+ function renderMfenced(node, children) {
905
947
  const [open, close, separatorsStr] = [
906
948
  NodeTool.getAttr(node, 'open', '('),
907
949
  NodeTool.getAttr(node, 'close', ')'),
908
950
  NodeTool.getAttr(node, 'separators', ',')
909
951
  ];
952
+
953
+ // Handle special case for vectors inside brackets
954
+ if (open === '[' && close === ']') {
955
+ const parts = renderChildren(children);
956
+ // Join parts with comma and space, preserving vector notation
957
+ const content = parts.join(', ');
958
+ return `\\left[${content}\\right]`;
959
+ }
960
+
961
+ // Handle special case for coordinates
962
+ if (open === '(' && close === ')') {
963
+ const parts = renderChildren(children);
964
+ // Join parts with semicolon
965
+ const content = parts.join(';');
966
+ return `\\left(${content}\\right)`;
967
+ }
968
+
910
969
  const [left, right] = [
911
970
  Brackets.parseLeft(open),
912
971
  Brackets.parseRight(close)
@@ -966,19 +1025,49 @@ function renderMmultiscripts(node, children) {
966
1025
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
967
1026
  }
968
1027
 
969
- function renderMover(node, children){
1028
+ function renderMover(node, children) {
970
1029
  const nodes = flattenNodeTreeByNodeName(node, 'mover');
971
1030
  let result = undefined;
1031
+
1032
+ // Get the base node and check if it's a subscript or mrow
1033
+ const baseNode = children[0];
1034
+ const nodeName = NodeTool.getNodeName(baseNode);
1035
+ const isSubscript = nodeName === 'msub';
1036
+ const isMrow = nodeName === 'mrow';
1037
+
1038
+ if (isSubscript) {
1039
+ // Handle case like n₂ with arrow
1040
+ const base = parse(baseNode);
1041
+ return `\\overrightarrow{${base}}`;
1042
+ }
1043
+
1044
+ if (isMrow) {
1045
+ // Handle case like AB or AI
1046
+ const base = parse(baseNode);
1047
+ const overNode = children[1];
1048
+ const overText = NodeTool.getNodeText(overNode).trim();
1049
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1050
+
1051
+ if (overText === "→" && isAccent) {
1052
+ return `\\overrightarrow{${base}}`;
1053
+ }
1054
+ }
1055
+
972
1056
  for(let i = 0; i < nodes.length - 1; i++) {
973
- if(!result){ result = parse(nodes[i]); }
974
- const over = parse(nodes[i + 1]);
975
- const template = getMatchValueByChar({
976
- decimals: MathSymbol.overScript.decimals,
977
- values: MathSymbol.overScript.templates,
978
- judgeChar: over,
979
- defaultValue: "@1^{@2}"
980
- });
981
- result = renderTemplate(template.replace("@v", "@1"), [result, over]);
1057
+ if(!result) {
1058
+ result = parse(nodes[i]);
1059
+ }
1060
+
1061
+ const overNode = nodes[i + 1];
1062
+ const overText = NodeTool.getNodeText(overNode).trim();
1063
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1064
+
1065
+ if (overText === "" && isAccent) {
1066
+ return `\\overrightarrow{${result}}`;
1067
+ }
1068
+
1069
+ const over = parse(overNode);
1070
+ result = `${result}^{${over}}`;
982
1071
  }
983
1072
  return result;
984
1073
  }
@@ -986,162 +1075,119 @@ function renderMover(node, children){
986
1075
  function renderMunder(node, children){
987
1076
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
988
1077
  let result = undefined;
989
-
990
- // Early processing for arrow case
991
- const baseNode = children[0];
992
- if (baseNode && NodeTool.getNodeName(baseNode) === "mo") {
993
- const baseText = NodeTool.getNodeText(baseNode).trim();
994
- if (baseText === "→") {
995
- // This is an arrow with under script
996
- const underNode = children[1];
997
- if (!underNode || NodeTool.getNodeName(underNode) === "mrow" && NodeTool.getNodeText(underNode).trim() === "") {
998
- // Empty under script or mrow
999
- console.log("Arrow with empty underscript, using \\underset{}");
1000
- return "\\underset{}{\\rightarrow}";
1001
- } else {
1002
- // Non-empty under script
1003
- const under = parse(underNode);
1004
- console.log("Arrow with underscript:", under);
1005
- return `\\underset{${under}}{\\rightarrow}`;
1006
- }
1007
- }
1008
- }
1009
-
1010
1078
  for(let i = 0; i < nodes.length - 1; i++) {
1011
1079
  if(!result){ result = parse(nodes[i]); }
1012
1080
 
1013
1081
  const underNode = nodes[i + 1];
1014
1082
  const underText = NodeTool.getNodeText(underNode).trim();
1015
- const baseText = NodeTool.getNodeText(nodes[i]).trim();
1083
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1016
1084
 
1017
- // Skip processing for already processed cases
1018
- if (baseText === "→") {
1019
- continue;
1085
+ // Special handling for arrow accent
1086
+ if (underText === "→" && isAccent) {
1087
+ return `\\underset{${result}}{\\rightarrow}`;
1020
1088
  }
1021
1089
 
1022
- // Always use underset for operators to avoid \\limits errors
1023
- if (NodeTool.getNodeName(nodes[i]) === "mo") {
1024
- const under = parse(underNode);
1025
- result = `\\underset{${under}}{${result}}`;
1026
- } else {
1027
- const template = getMatchValueByChar({
1028
- decimals: MathSymbol.underScript.decimals,
1029
- values: MathSymbol.underScript.templates,
1030
- judgeChar: underText,
1031
- defaultValue: "@1_{@2}" // Use simple subscript instead of \limits
1032
- });
1033
-
1034
- const under = parse(underNode);
1035
- result = renderTemplate(template.replace("@v", "@1"), [result, under]);
1036
- }
1090
+ const under = parse(underNode);
1091
+ const template = getMatchValueByChar({
1092
+ decimals: MathSymbol.underScript.decimals,
1093
+ values: MathSymbol.underScript.templates,
1094
+ judgeChar: underText,
1095
+ defaultValue: "@1_{@2}"
1096
+ });
1097
+ result = renderTemplate(template.replace("@v", "@1"), [result, under]);
1037
1098
  }
1038
1099
  return result;
1039
1100
  }
1040
1101
 
1041
- function renderMunderover(node, children) {
1102
+ function renderMunderover(node, children){
1042
1103
  const nodes = flattenNodeTreeByNodeName(node, 'munderover');
1043
1104
  let result = undefined;
1044
-
1045
- if(nodes.length === 3) {
1046
- const baseNode = nodes[0];
1047
- const baseText = NodeTool.getNodeText(baseNode).trim();
1105
+ for(let i = 0; i < nodes.length - 1; i++) {
1106
+ if(!result){ result = parse(nodes[i]); }
1048
1107
 
1049
- // Special handling for arrow
1050
- if (baseText === "→") {
1051
- const under = parse(nodes[1]);
1052
- const over = parse(nodes[2]);
1053
- return `\\overset{${over}}{\\underset{${under}}{\\rightarrow}}`;
1054
- } else {
1055
- const base = parse(baseNode);
1056
- const under = parse(nodes[1]);
1057
- const over = parse(nodes[2]);
1058
- return `${base}\\limits_{${under}}^{${over}}`;
1108
+ const overNode = nodes[i + 1];
1109
+ const overText = NodeTool.getNodeText(overNode).trim();
1110
+ const underNode = nodes[i + 2];
1111
+ const underText = NodeTool.getNodeText(underNode).trim();
1112
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1113
+
1114
+ // Special handling for arrow accent
1115
+ if (overText === "→" && isAccent) {
1116
+ return `\\overset{${result}}{\\underset{${underText}}{\\rightarrow}}`;
1059
1117
  }
1060
- }
1061
-
1062
- for(let i = 0; i < nodes.length - 2; i++) {
1063
- if(!result){ result = parse(nodes[i]); }
1064
- const under = parse(nodes[i + 1]);
1065
- const over = parse(nodes[i + 2]);
1066
- result = renderTemplate("@1\\limits_{@2}^{@3}", [result, under, over]);
1118
+
1119
+ const over = parse(overNode);
1120
+ const under = parse(underNode);
1121
+ const template = getMatchValueByChar({
1122
+ decimals: MathSymbol.underoverScript.decimals,
1123
+ values: MathSymbol.underoverScript.templates,
1124
+ judgeChar: overText,
1125
+ defaultValue: "@1_{@2}^{@3}"
1126
+ });
1127
+ result = renderTemplate(template.replace("@v", "@1"), [over, under]);
1067
1128
  }
1068
1129
  return result;
1069
1130
  }
1070
1131
 
1071
- function flattenNodeTreeByNodeName(root, nodeName) {
1072
- let result = [];
1073
- const children = NodeTool.getChildren(root);
1074
- Array.prototype.forEach.call(children, (node) => {
1075
- if (NodeTool.getNodeName(node) === nodeName) {
1076
- result = result.concat(flattenNodeTreeByNodeName(node, nodeName));
1077
- } else {
1078
- result.push(node);
1079
- }
1080
- });
1081
- return result;
1082
- }
1083
-
1084
-
1085
- function getMatchValueByChar(params) {
1086
- const {decimals, values, judgeChar, defaultValue=null} = params;
1087
- if (judgeChar && judgeChar.length === 1) {
1088
- const index = decimals.indexOf(judgeChar.charCodeAt(0));
1089
- if (index > -1) {
1090
- return values[index];
1132
+ function renderMphantom(node, children){
1133
+ const nodes = flattenNodeTreeByNodeName(node, 'mphantom');
1134
+ let result = undefined;
1135
+ for(let i = 0; i < nodes.length - 1; i++) {
1136
+ if(!result){ result = parse(nodes[i]); }
1137
+
1138
+ const phantomNode = nodes[i + 1];
1139
+ const phantomText = NodeTool.getNodeText(phantomNode).trim();
1140
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1141
+
1142
+ // Special handling for arrow accent
1143
+ if (phantomText === "→" && isAccent) {
1144
+ return `\\overrightarrow{${result}}`;
1091
1145
  }
1146
+
1147
+ const phantom = parse(phantomNode);
1148
+ const template = getMatchValueByChar({
1149
+ decimals: MathSymbol.phantomScript.decimals,
1150
+ values: MathSymbol.phantomScript.templates,
1151
+ judgeChar: phantomText,
1152
+ defaultValue: "@1^{@2}"
1153
+ });
1154
+ result = renderTemplate(template.replace("@v", "@1"), [result, phantom]);
1092
1155
  }
1093
- return defaultValue;
1094
- }
1095
-
1096
- // https://developer.mozilla.org/en-US/docs/Web/MathML/Element/mphantom
1097
- // FIXME :)
1098
- function renderMphantom(node, children) {
1099
- return '';
1100
- }
1101
-
1102
-
1103
-
1104
- function getRender_default(template) {
1105
- return function(node, children) {
1106
- const parts = renderChildren(children);
1107
- return renderTemplate(template, parts)
1108
- }
1156
+ return result;
1109
1157
  }
1110
1158
 
1111
- function renderTemplate(template, values) {
1112
- return template.replace(/\@\d+/g, (m) => {
1113
- const idx = parseInt(m.substring(1, m.length)) - 1;
1114
- return values[idx];
1159
+ function renderTemplate(template, args) {
1160
+ return template.replace(/@(\d+)/g, (match, index) => {
1161
+ const arg = args[index - 1];
1162
+ return arg || match;
1115
1163
  });
1116
1164
  }
1117
1165
 
1118
- function getRender_joinSeparator(template, separator = '') {
1119
- return function(node, children) {
1120
- const parts = renderChildren(children);
1121
- return template.replace("@content", parts.join(separator));
1122
- }
1166
+ function getMatchValueByChar(options) {
1167
+ const { decimals, values, judgeChar, defaultValue } = options;
1168
+ const match = values.find(value => value.judgeChar === judgeChar);
1169
+ return match || defaultValue;
1123
1170
  }
1124
1171
 
1125
- function getRender_joinSeparators(template, separators) {
1126
- return function(node, children) {
1127
- const parts = renderChildren(children);
1128
- let content = '';
1129
- if(separators.length === 0){
1130
- content = parts.join('');
1131
- } else {
1132
- content = parts.reduce((accumulator, part, index) => {
1133
- accumulator += part;
1134
- if(index < parts.length - 1){
1135
- accumulator += (separators[index] || separators[separators.length - 1]);
1136
- }
1137
- return accumulator;
1138
- }, '');
1139
- }
1140
- return template.replace("@content", content);
1172
+ function flattenNodeTreeByNodeName(node, nodeName) {
1173
+ const nodes = [];
1174
+ const children = NodeTool.getChildren(node);
1175
+ if (children && children.length > 0) {
1176
+ // Convert HTMLCollection to Array before using forEach
1177
+ Array.from(children).forEach(child => {
1178
+ if (NodeTool.getNodeName(child) === nodeName) {
1179
+ nodes.push(child);
1180
+ } else {
1181
+ // Recursively search in child nodes
1182
+ const childNodes = flattenNodeTreeByNodeName(child, nodeName);
1183
+ nodes.push(...childNodes);
1184
+ }
1185
+ });
1141
1186
  }
1187
+ return nodes;
1142
1188
  }
1143
1189
 
1144
- // Add exports at the end of file
1190
+ // Export the convert function
1145
1191
  var mathml2latex = {
1146
1192
  convert: convert
1147
1193
  };