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