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