ed-mathml2tex 0.0.8 → 0.0.9

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.
@@ -147,7 +147,9 @@ const NodeTool = {
147
147
  return node.children;
148
148
  },
149
149
  getNodeName: function(node) {
150
- return node.tagName.toLowerCase();
150
+ if (!node) return '';
151
+ const localName = node.localName || node.nodeName;
152
+ return localName ? localName.toLowerCase() : '';
151
153
  },
152
154
  getNodeText: function(node) {
153
155
  return node.textContent;
@@ -704,9 +706,9 @@ function parseLeaf(node) {
704
706
  function parseOperator(node) {
705
707
  let it = NodeTool.getNodeText(node).trim();
706
708
 
707
- // Special case for arrow (→)
709
+ // Special case for arrow (→) with proper spacing
708
710
  if (it === "→") {
709
- return "\\rightarrow";
711
+ return " \\rightarrow "; // Add spaces around arrow
710
712
  }
711
713
 
712
714
  it = MathSymbol.parseOperator(it);
@@ -833,7 +835,28 @@ function getRender(node) {
833
835
  const nodeName = NodeTool.getNodeName(node);
834
836
  switch(nodeName){
835
837
  case 'msub':
836
- render = getRender_default("@1_{@2}");
838
+ render = function(node, children) {
839
+ if (!children || children.length < 2) return '';
840
+
841
+ const base = parse(children[0]);
842
+ if (!base) return '';
843
+
844
+ const sub = children[1];
845
+ if (!sub) return base;
846
+
847
+ // Handle nested subscript with empty mrow
848
+ if (NodeTool.getNodeName(sub) === 'msub' &&
849
+ sub.firstChild &&
850
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
851
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
852
+ const lastChild = sub.lastChild;
853
+ if (!lastChild) return base;
854
+ return `${base}_${parse(lastChild)}`;
855
+ }
856
+
857
+ // Regular subscript
858
+ return `${base}_{${parse(sub)}}`;
859
+ };
837
860
  break;
838
861
  case 'msup':
839
862
  render = getRender_default("@1^{@2}");
@@ -895,10 +918,32 @@ function getRender(node) {
895
918
 
896
919
  function renderTable(node, children) {
897
920
  const template = "\\begin{array}{l}@content\\end{array}";
898
- const render = getRender_joinSeparator(template, "\\\\");
921
+ // Remove extra backslash and add proper spacing
922
+ const render = getRender_joinSeparator(template, " \\\\ ");
899
923
  return render(node, children);
900
924
  }
901
925
 
926
+ function renderMover(node, children) {
927
+ // Check if children exists and has enough elements
928
+ if (!children || children.length < 2) return '';
929
+
930
+ const baseNode = children[0];
931
+ const overNode = children[1];
932
+
933
+ // Check if nodes exist before accessing
934
+ if (!baseNode || !overNode) return '';
935
+
936
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
937
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
938
+
939
+ // Handle vector notation
940
+ if (overText === "→" && isAccent) {
941
+ return `\\vec{${parse(baseNode)}}`;
942
+ }
943
+
944
+ return parse(baseNode);
945
+ }
946
+
902
947
  function renderMfenced(node, children) {
903
948
  const [open, close, separatorsStr] = [
904
949
  NodeTool.getAttr(node, 'open', '('),
@@ -909,9 +954,10 @@ function renderMfenced(node, children) {
909
954
  const parts = renderChildren(children);
910
955
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
911
956
 
912
- // Handle empty open/close brackets
913
- if (open === '' && close === '|') {
914
- return `|${content}|`;
957
+ // Handle system of equations with curly brace
958
+ if (open === '{') {
959
+ // If no closing brace specified, use \right.
960
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
915
961
  }
916
962
 
917
963
  // Handle absolute value notation
@@ -919,12 +965,7 @@ function renderMfenced(node, children) {
919
965
  return `\\left|${content}\\right|`;
920
966
  }
921
967
 
922
- // Regular parentheses
923
- if (open === '(' && close === ')') {
924
- return `\\left(${content}\\right)`;
925
- }
926
-
927
- // Default case
968
+ // Regular cases with proper closing
928
969
  return `\\left${open}${content}\\right${close || '.'}`;
929
970
  }
930
971
 
@@ -1003,62 +1044,6 @@ function renderMmultiscripts(node, children) {
1003
1044
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1004
1045
  }
1005
1046
 
1006
- function renderMover(node, children) {
1007
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1008
- let result = undefined;
1009
-
1010
- // Get the base node and check if it's a subscript or mrow
1011
- const baseNode = children[0];
1012
- const overNode = children[1];
1013
- const nodeName = NodeTool.getNodeName(baseNode);
1014
- const isSubscript = nodeName === 'msub';
1015
- const isMrow = nodeName === 'mrow';
1016
-
1017
- // Handle case where the base is an arrow and mrow is above
1018
- const baseText = NodeTool.getNodeText(baseNode).trim();
1019
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1020
- return "\\rightarrow";
1021
- }
1022
-
1023
- if (isSubscript) {
1024
- // Handle case like n₂ with arrow
1025
- const base = parse(baseNode);
1026
- return `\\overrightarrow{${base}}`;
1027
- }
1028
-
1029
- if (isMrow) {
1030
- // Handle case like 0 with arrow
1031
- const base = parse(baseNode);
1032
- const overText = NodeTool.getNodeText(overNode).trim();
1033
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1034
-
1035
- if (overText === "→" && isAccent) {
1036
- return `\\overrightarrow{${base}}`;
1037
- }
1038
- }
1039
-
1040
- // Handle case where there is only an arrow with no base
1041
- const overText = NodeTool.getNodeText(overNode).trim();
1042
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1043
- return `\\rightarrow`; // Return just the arrow
1044
- }
1045
-
1046
- for (let i = 0; i < nodes.length - 1; i++) {
1047
- if (!result) {
1048
- result = parse(nodes[i]);
1049
- }
1050
-
1051
- const _overNode = nodes[i + 1];
1052
- const overText = NodeTool.getNodeText(_overNode).trim();
1053
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1054
-
1055
- if (overText === "→" && isAccent) {
1056
- return `\\overrightarrow{${result}}`;
1057
- }
1058
- }
1059
- return result;
1060
- }
1061
-
1062
1047
  function renderMunder(node, children){
1063
1048
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1064
1049
  let result = undefined;
@@ -145,7 +145,9 @@ const NodeTool = {
145
145
  return node.children;
146
146
  },
147
147
  getNodeName: function(node) {
148
- return node.tagName.toLowerCase();
148
+ if (!node) return '';
149
+ const localName = node.localName || node.nodeName;
150
+ return localName ? localName.toLowerCase() : '';
149
151
  },
150
152
  getNodeText: function(node) {
151
153
  return node.textContent;
@@ -702,9 +704,9 @@ function parseLeaf(node) {
702
704
  function parseOperator(node) {
703
705
  let it = NodeTool.getNodeText(node).trim();
704
706
 
705
- // Special case for arrow (→)
707
+ // Special case for arrow (→) with proper spacing
706
708
  if (it === "→") {
707
- return "\\rightarrow";
709
+ return " \\rightarrow "; // Add spaces around arrow
708
710
  }
709
711
 
710
712
  it = MathSymbol.parseOperator(it);
@@ -831,7 +833,28 @@ function getRender(node) {
831
833
  const nodeName = NodeTool.getNodeName(node);
832
834
  switch(nodeName){
833
835
  case 'msub':
834
- render = getRender_default("@1_{@2}");
836
+ render = function(node, children) {
837
+ if (!children || children.length < 2) return '';
838
+
839
+ const base = parse(children[0]);
840
+ if (!base) return '';
841
+
842
+ const sub = children[1];
843
+ if (!sub) return base;
844
+
845
+ // Handle nested subscript with empty mrow
846
+ if (NodeTool.getNodeName(sub) === 'msub' &&
847
+ sub.firstChild &&
848
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
849
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
850
+ const lastChild = sub.lastChild;
851
+ if (!lastChild) return base;
852
+ return `${base}_${parse(lastChild)}`;
853
+ }
854
+
855
+ // Regular subscript
856
+ return `${base}_{${parse(sub)}}`;
857
+ };
835
858
  break;
836
859
  case 'msup':
837
860
  render = getRender_default("@1^{@2}");
@@ -893,10 +916,32 @@ function getRender(node) {
893
916
 
894
917
  function renderTable(node, children) {
895
918
  const template = "\\begin{array}{l}@content\\end{array}";
896
- const render = getRender_joinSeparator(template, "\\\\");
919
+ // Remove extra backslash and add proper spacing
920
+ const render = getRender_joinSeparator(template, " \\\\ ");
897
921
  return render(node, children);
898
922
  }
899
923
 
924
+ function renderMover(node, children) {
925
+ // Check if children exists and has enough elements
926
+ if (!children || children.length < 2) return '';
927
+
928
+ const baseNode = children[0];
929
+ const overNode = children[1];
930
+
931
+ // Check if nodes exist before accessing
932
+ if (!baseNode || !overNode) return '';
933
+
934
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
935
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
936
+
937
+ // Handle vector notation
938
+ if (overText === "→" && isAccent) {
939
+ return `\\vec{${parse(baseNode)}}`;
940
+ }
941
+
942
+ return parse(baseNode);
943
+ }
944
+
900
945
  function renderMfenced(node, children) {
901
946
  const [open, close, separatorsStr] = [
902
947
  NodeTool.getAttr(node, 'open', '('),
@@ -907,9 +952,10 @@ function renderMfenced(node, children) {
907
952
  const parts = renderChildren(children);
908
953
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
909
954
 
910
- // Handle empty open/close brackets
911
- if (open === '' && close === '|') {
912
- return `|${content}|`;
955
+ // Handle system of equations with curly brace
956
+ if (open === '{') {
957
+ // If no closing brace specified, use \right.
958
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
913
959
  }
914
960
 
915
961
  // Handle absolute value notation
@@ -917,12 +963,7 @@ function renderMfenced(node, children) {
917
963
  return `\\left|${content}\\right|`;
918
964
  }
919
965
 
920
- // Regular parentheses
921
- if (open === '(' && close === ')') {
922
- return `\\left(${content}\\right)`;
923
- }
924
-
925
- // Default case
966
+ // Regular cases with proper closing
926
967
  return `\\left${open}${content}\\right${close || '.'}`;
927
968
  }
928
969
 
@@ -1001,62 +1042,6 @@ function renderMmultiscripts(node, children) {
1001
1042
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1002
1043
  }
1003
1044
 
1004
- function renderMover(node, children) {
1005
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1006
- let result = undefined;
1007
-
1008
- // Get the base node and check if it's a subscript or mrow
1009
- const baseNode = children[0];
1010
- const overNode = children[1];
1011
- const nodeName = NodeTool.getNodeName(baseNode);
1012
- const isSubscript = nodeName === 'msub';
1013
- const isMrow = nodeName === 'mrow';
1014
-
1015
- // Handle case where the base is an arrow and mrow is above
1016
- const baseText = NodeTool.getNodeText(baseNode).trim();
1017
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1018
- return "\\rightarrow";
1019
- }
1020
-
1021
- if (isSubscript) {
1022
- // Handle case like n₂ with arrow
1023
- const base = parse(baseNode);
1024
- return `\\overrightarrow{${base}}`;
1025
- }
1026
-
1027
- if (isMrow) {
1028
- // Handle case like 0 with arrow
1029
- const base = parse(baseNode);
1030
- const overText = NodeTool.getNodeText(overNode).trim();
1031
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1032
-
1033
- if (overText === "→" && isAccent) {
1034
- return `\\overrightarrow{${base}}`;
1035
- }
1036
- }
1037
-
1038
- // Handle case where there is only an arrow with no base
1039
- const overText = NodeTool.getNodeText(overNode).trim();
1040
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1041
- return `\\rightarrow`; // Return just the arrow
1042
- }
1043
-
1044
- for (let i = 0; i < nodes.length - 1; i++) {
1045
- if (!result) {
1046
- result = parse(nodes[i]);
1047
- }
1048
-
1049
- const _overNode = nodes[i + 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{${result}}`;
1055
- }
1056
- }
1057
- return result;
1058
- }
1059
-
1060
1045
  function renderMunder(node, children){
1061
1046
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1062
1047
  let result = undefined;
@@ -151,7 +151,9 @@
151
151
  return node.children;
152
152
  },
153
153
  getNodeName: function(node) {
154
- return node.tagName.toLowerCase();
154
+ if (!node) return '';
155
+ const localName = node.localName || node.nodeName;
156
+ return localName ? localName.toLowerCase() : '';
155
157
  },
156
158
  getNodeText: function(node) {
157
159
  return node.textContent;
@@ -708,9 +710,9 @@
708
710
  function parseOperator(node) {
709
711
  let it = NodeTool.getNodeText(node).trim();
710
712
 
711
- // Special case for arrow (→)
713
+ // Special case for arrow (→) with proper spacing
712
714
  if (it === "→") {
713
- return "\\rightarrow";
715
+ return " \\rightarrow "; // Add spaces around arrow
714
716
  }
715
717
 
716
718
  it = MathSymbol.parseOperator(it);
@@ -837,7 +839,28 @@
837
839
  const nodeName = NodeTool.getNodeName(node);
838
840
  switch(nodeName){
839
841
  case 'msub':
840
- render = getRender_default("@1_{@2}");
842
+ render = function(node, children) {
843
+ if (!children || children.length < 2) return '';
844
+
845
+ const base = parse(children[0]);
846
+ if (!base) return '';
847
+
848
+ const sub = children[1];
849
+ if (!sub) return base;
850
+
851
+ // Handle nested subscript with empty mrow
852
+ if (NodeTool.getNodeName(sub) === 'msub' &&
853
+ sub.firstChild &&
854
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
855
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
856
+ const lastChild = sub.lastChild;
857
+ if (!lastChild) return base;
858
+ return `${base}_${parse(lastChild)}`;
859
+ }
860
+
861
+ // Regular subscript
862
+ return `${base}_{${parse(sub)}}`;
863
+ };
841
864
  break;
842
865
  case 'msup':
843
866
  render = getRender_default("@1^{@2}");
@@ -899,10 +922,32 @@
899
922
 
900
923
  function renderTable(node, children) {
901
924
  const template = "\\begin{array}{l}@content\\end{array}";
902
- const render = getRender_joinSeparator(template, "\\\\");
925
+ // Remove extra backslash and add proper spacing
926
+ const render = getRender_joinSeparator(template, " \\\\ ");
903
927
  return render(node, children);
904
928
  }
905
929
 
930
+ function renderMover(node, children) {
931
+ // Check if children exists and has enough elements
932
+ if (!children || children.length < 2) return '';
933
+
934
+ const baseNode = children[0];
935
+ const overNode = children[1];
936
+
937
+ // Check if nodes exist before accessing
938
+ if (!baseNode || !overNode) return '';
939
+
940
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
941
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
942
+
943
+ // Handle vector notation
944
+ if (overText === "→" && isAccent) {
945
+ return `\\vec{${parse(baseNode)}}`;
946
+ }
947
+
948
+ return parse(baseNode);
949
+ }
950
+
906
951
  function renderMfenced(node, children) {
907
952
  const [open, close, separatorsStr] = [
908
953
  NodeTool.getAttr(node, 'open', '('),
@@ -913,9 +958,10 @@
913
958
  const parts = renderChildren(children);
914
959
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
915
960
 
916
- // Handle empty open/close brackets
917
- if (open === '' && close === '|') {
918
- return `|${content}|`;
961
+ // Handle system of equations with curly brace
962
+ if (open === '{') {
963
+ // If no closing brace specified, use \right.
964
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
919
965
  }
920
966
 
921
967
  // Handle absolute value notation
@@ -923,12 +969,7 @@
923
969
  return `\\left|${content}\\right|`;
924
970
  }
925
971
 
926
- // Regular parentheses
927
- if (open === '(' && close === ')') {
928
- return `\\left(${content}\\right)`;
929
- }
930
-
931
- // Default case
972
+ // Regular cases with proper closing
932
973
  return `\\left${open}${content}\\right${close || '.'}`;
933
974
  }
934
975
 
@@ -1007,62 +1048,6 @@
1007
1048
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1008
1049
  }
1009
1050
 
1010
- function renderMover(node, children) {
1011
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1012
- let result = undefined;
1013
-
1014
- // Get the base node and check if it's a subscript or mrow
1015
- const baseNode = children[0];
1016
- const overNode = children[1];
1017
- const nodeName = NodeTool.getNodeName(baseNode);
1018
- const isSubscript = nodeName === 'msub';
1019
- const isMrow = nodeName === 'mrow';
1020
-
1021
- // Handle case where the base is an arrow and mrow is above
1022
- const baseText = NodeTool.getNodeText(baseNode).trim();
1023
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1024
- return "\\rightarrow";
1025
- }
1026
-
1027
- if (isSubscript) {
1028
- // Handle case like n₂ with arrow
1029
- const base = parse(baseNode);
1030
- return `\\overrightarrow{${base}}`;
1031
- }
1032
-
1033
- if (isMrow) {
1034
- // Handle case like 0 with arrow
1035
- const base = parse(baseNode);
1036
- const overText = NodeTool.getNodeText(overNode).trim();
1037
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1038
-
1039
- if (overText === "→" && isAccent) {
1040
- return `\\overrightarrow{${base}}`;
1041
- }
1042
- }
1043
-
1044
- // Handle case where there is only an arrow with no base
1045
- const overText = NodeTool.getNodeText(overNode).trim();
1046
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1047
- return `\\rightarrow`; // Return just the arrow
1048
- }
1049
-
1050
- for (let i = 0; i < nodes.length - 1; i++) {
1051
- if (!result) {
1052
- result = parse(nodes[i]);
1053
- }
1054
-
1055
- const _overNode = nodes[i + 1];
1056
- const overText = NodeTool.getNodeText(_overNode).trim();
1057
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1058
-
1059
- if (overText === "→" && isAccent) {
1060
- return `\\overrightarrow{${result}}`;
1061
- }
1062
- }
1063
- return result;
1064
- }
1065
-
1066
1051
  function renderMunder(node, children){
1067
1052
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1068
1053
  let result = undefined;
@@ -147,7 +147,9 @@ const NodeTool = {
147
147
  return node.children;
148
148
  },
149
149
  getNodeName: function(node) {
150
- return node.tagName.toLowerCase();
150
+ if (!node) return '';
151
+ const localName = node.localName || node.nodeName;
152
+ return localName ? localName.toLowerCase() : '';
151
153
  },
152
154
  getNodeText: function(node) {
153
155
  return node.textContent;
@@ -704,9 +706,9 @@ function parseLeaf(node) {
704
706
  function parseOperator(node) {
705
707
  let it = NodeTool.getNodeText(node).trim();
706
708
 
707
- // Special case for arrow (→)
709
+ // Special case for arrow (→) with proper spacing
708
710
  if (it === "→") {
709
- return "\\rightarrow";
711
+ return " \\rightarrow "; // Add spaces around arrow
710
712
  }
711
713
 
712
714
  it = MathSymbol.parseOperator(it);
@@ -833,7 +835,28 @@ function getRender(node) {
833
835
  const nodeName = NodeTool.getNodeName(node);
834
836
  switch(nodeName){
835
837
  case 'msub':
836
- render = getRender_default("@1_{@2}");
838
+ render = function(node, children) {
839
+ if (!children || children.length < 2) return '';
840
+
841
+ const base = parse(children[0]);
842
+ if (!base) return '';
843
+
844
+ const sub = children[1];
845
+ if (!sub) return base;
846
+
847
+ // Handle nested subscript with empty mrow
848
+ if (NodeTool.getNodeName(sub) === 'msub' &&
849
+ sub.firstChild &&
850
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
851
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
852
+ const lastChild = sub.lastChild;
853
+ if (!lastChild) return base;
854
+ return `${base}_${parse(lastChild)}`;
855
+ }
856
+
857
+ // Regular subscript
858
+ return `${base}_{${parse(sub)}}`;
859
+ };
837
860
  break;
838
861
  case 'msup':
839
862
  render = getRender_default("@1^{@2}");
@@ -895,10 +918,32 @@ function getRender(node) {
895
918
 
896
919
  function renderTable(node, children) {
897
920
  const template = "\\begin{array}{l}@content\\end{array}";
898
- const render = getRender_joinSeparator(template, "\\\\");
921
+ // Remove extra backslash and add proper spacing
922
+ const render = getRender_joinSeparator(template, " \\\\ ");
899
923
  return render(node, children);
900
924
  }
901
925
 
926
+ function renderMover(node, children) {
927
+ // Check if children exists and has enough elements
928
+ if (!children || children.length < 2) return '';
929
+
930
+ const baseNode = children[0];
931
+ const overNode = children[1];
932
+
933
+ // Check if nodes exist before accessing
934
+ if (!baseNode || !overNode) return '';
935
+
936
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
937
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
938
+
939
+ // Handle vector notation
940
+ if (overText === "→" && isAccent) {
941
+ return `\\vec{${parse(baseNode)}}`;
942
+ }
943
+
944
+ return parse(baseNode);
945
+ }
946
+
902
947
  function renderMfenced(node, children) {
903
948
  const [open, close, separatorsStr] = [
904
949
  NodeTool.getAttr(node, 'open', '('),
@@ -909,9 +954,10 @@ function renderMfenced(node, children) {
909
954
  const parts = renderChildren(children);
910
955
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
911
956
 
912
- // Handle empty open/close brackets
913
- if (open === '' && close === '|') {
914
- return `|${content}|`;
957
+ // Handle system of equations with curly brace
958
+ if (open === '{') {
959
+ // If no closing brace specified, use \right.
960
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
915
961
  }
916
962
 
917
963
  // Handle absolute value notation
@@ -919,12 +965,7 @@ function renderMfenced(node, children) {
919
965
  return `\\left|${content}\\right|`;
920
966
  }
921
967
 
922
- // Regular parentheses
923
- if (open === '(' && close === ')') {
924
- return `\\left(${content}\\right)`;
925
- }
926
-
927
- // Default case
968
+ // Regular cases with proper closing
928
969
  return `\\left${open}${content}\\right${close || '.'}`;
929
970
  }
930
971
 
@@ -1003,62 +1044,6 @@ function renderMmultiscripts(node, children) {
1003
1044
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1004
1045
  }
1005
1046
 
1006
- function renderMover(node, children) {
1007
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1008
- let result = undefined;
1009
-
1010
- // Get the base node and check if it's a subscript or mrow
1011
- const baseNode = children[0];
1012
- const overNode = children[1];
1013
- const nodeName = NodeTool.getNodeName(baseNode);
1014
- const isSubscript = nodeName === 'msub';
1015
- const isMrow = nodeName === 'mrow';
1016
-
1017
- // Handle case where the base is an arrow and mrow is above
1018
- const baseText = NodeTool.getNodeText(baseNode).trim();
1019
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1020
- return "\\rightarrow";
1021
- }
1022
-
1023
- if (isSubscript) {
1024
- // Handle case like n₂ with arrow
1025
- const base = parse(baseNode);
1026
- return `\\overrightarrow{${base}}`;
1027
- }
1028
-
1029
- if (isMrow) {
1030
- // Handle case like 0 with arrow
1031
- const base = parse(baseNode);
1032
- const overText = NodeTool.getNodeText(overNode).trim();
1033
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1034
-
1035
- if (overText === "→" && isAccent) {
1036
- return `\\overrightarrow{${base}}`;
1037
- }
1038
- }
1039
-
1040
- // Handle case where there is only an arrow with no base
1041
- const overText = NodeTool.getNodeText(overNode).trim();
1042
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1043
- return `\\rightarrow`; // Return just the arrow
1044
- }
1045
-
1046
- for (let i = 0; i < nodes.length - 1; i++) {
1047
- if (!result) {
1048
- result = parse(nodes[i]);
1049
- }
1050
-
1051
- const _overNode = nodes[i + 1];
1052
- const overText = NodeTool.getNodeText(_overNode).trim();
1053
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1054
-
1055
- if (overText === "→" && isAccent) {
1056
- return `\\overrightarrow{${result}}`;
1057
- }
1058
- }
1059
- return result;
1060
- }
1061
-
1062
1047
  function renderMunder(node, children){
1063
1048
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1064
1049
  let result = undefined;
@@ -145,7 +145,9 @@ const NodeTool = {
145
145
  return node.children;
146
146
  },
147
147
  getNodeName: function(node) {
148
- return node.tagName.toLowerCase();
148
+ if (!node) return '';
149
+ const localName = node.localName || node.nodeName;
150
+ return localName ? localName.toLowerCase() : '';
149
151
  },
150
152
  getNodeText: function(node) {
151
153
  return node.textContent;
@@ -702,9 +704,9 @@ function parseLeaf(node) {
702
704
  function parseOperator(node) {
703
705
  let it = NodeTool.getNodeText(node).trim();
704
706
 
705
- // Special case for arrow (→)
707
+ // Special case for arrow (→) with proper spacing
706
708
  if (it === "→") {
707
- return "\\rightarrow";
709
+ return " \\rightarrow "; // Add spaces around arrow
708
710
  }
709
711
 
710
712
  it = MathSymbol.parseOperator(it);
@@ -831,7 +833,28 @@ function getRender(node) {
831
833
  const nodeName = NodeTool.getNodeName(node);
832
834
  switch(nodeName){
833
835
  case 'msub':
834
- render = getRender_default("@1_{@2}");
836
+ render = function(node, children) {
837
+ if (!children || children.length < 2) return '';
838
+
839
+ const base = parse(children[0]);
840
+ if (!base) return '';
841
+
842
+ const sub = children[1];
843
+ if (!sub) return base;
844
+
845
+ // Handle nested subscript with empty mrow
846
+ if (NodeTool.getNodeName(sub) === 'msub' &&
847
+ sub.firstChild &&
848
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
849
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
850
+ const lastChild = sub.lastChild;
851
+ if (!lastChild) return base;
852
+ return `${base}_${parse(lastChild)}`;
853
+ }
854
+
855
+ // Regular subscript
856
+ return `${base}_{${parse(sub)}}`;
857
+ };
835
858
  break;
836
859
  case 'msup':
837
860
  render = getRender_default("@1^{@2}");
@@ -893,10 +916,32 @@ function getRender(node) {
893
916
 
894
917
  function renderTable(node, children) {
895
918
  const template = "\\begin{array}{l}@content\\end{array}";
896
- const render = getRender_joinSeparator(template, "\\\\");
919
+ // Remove extra backslash and add proper spacing
920
+ const render = getRender_joinSeparator(template, " \\\\ ");
897
921
  return render(node, children);
898
922
  }
899
923
 
924
+ function renderMover(node, children) {
925
+ // Check if children exists and has enough elements
926
+ if (!children || children.length < 2) return '';
927
+
928
+ const baseNode = children[0];
929
+ const overNode = children[1];
930
+
931
+ // Check if nodes exist before accessing
932
+ if (!baseNode || !overNode) return '';
933
+
934
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
935
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
936
+
937
+ // Handle vector notation
938
+ if (overText === "→" && isAccent) {
939
+ return `\\vec{${parse(baseNode)}}`;
940
+ }
941
+
942
+ return parse(baseNode);
943
+ }
944
+
900
945
  function renderMfenced(node, children) {
901
946
  const [open, close, separatorsStr] = [
902
947
  NodeTool.getAttr(node, 'open', '('),
@@ -907,9 +952,10 @@ function renderMfenced(node, children) {
907
952
  const parts = renderChildren(children);
908
953
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
909
954
 
910
- // Handle empty open/close brackets
911
- if (open === '' && close === '|') {
912
- return `|${content}|`;
955
+ // Handle system of equations with curly brace
956
+ if (open === '{') {
957
+ // If no closing brace specified, use \right.
958
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
913
959
  }
914
960
 
915
961
  // Handle absolute value notation
@@ -917,12 +963,7 @@ function renderMfenced(node, children) {
917
963
  return `\\left|${content}\\right|`;
918
964
  }
919
965
 
920
- // Regular parentheses
921
- if (open === '(' && close === ')') {
922
- return `\\left(${content}\\right)`;
923
- }
924
-
925
- // Default case
966
+ // Regular cases with proper closing
926
967
  return `\\left${open}${content}\\right${close || '.'}`;
927
968
  }
928
969
 
@@ -1001,62 +1042,6 @@ function renderMmultiscripts(node, children) {
1001
1042
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1002
1043
  }
1003
1044
 
1004
- function renderMover(node, children) {
1005
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1006
- let result = undefined;
1007
-
1008
- // Get the base node and check if it's a subscript or mrow
1009
- const baseNode = children[0];
1010
- const overNode = children[1];
1011
- const nodeName = NodeTool.getNodeName(baseNode);
1012
- const isSubscript = nodeName === 'msub';
1013
- const isMrow = nodeName === 'mrow';
1014
-
1015
- // Handle case where the base is an arrow and mrow is above
1016
- const baseText = NodeTool.getNodeText(baseNode).trim();
1017
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1018
- return "\\rightarrow";
1019
- }
1020
-
1021
- if (isSubscript) {
1022
- // Handle case like n₂ with arrow
1023
- const base = parse(baseNode);
1024
- return `\\overrightarrow{${base}}`;
1025
- }
1026
-
1027
- if (isMrow) {
1028
- // Handle case like 0 with arrow
1029
- const base = parse(baseNode);
1030
- const overText = NodeTool.getNodeText(overNode).trim();
1031
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1032
-
1033
- if (overText === "→" && isAccent) {
1034
- return `\\overrightarrow{${base}}`;
1035
- }
1036
- }
1037
-
1038
- // Handle case where there is only an arrow with no base
1039
- const overText = NodeTool.getNodeText(overNode).trim();
1040
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1041
- return `\\rightarrow`; // Return just the arrow
1042
- }
1043
-
1044
- for (let i = 0; i < nodes.length - 1; i++) {
1045
- if (!result) {
1046
- result = parse(nodes[i]);
1047
- }
1048
-
1049
- const _overNode = nodes[i + 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{${result}}`;
1055
- }
1056
- }
1057
- return result;
1058
- }
1059
-
1060
1045
  function renderMunder(node, children){
1061
1046
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1062
1047
  let result = undefined;
@@ -151,7 +151,9 @@
151
151
  return node.children;
152
152
  },
153
153
  getNodeName: function(node) {
154
- return node.tagName.toLowerCase();
154
+ if (!node) return '';
155
+ const localName = node.localName || node.nodeName;
156
+ return localName ? localName.toLowerCase() : '';
155
157
  },
156
158
  getNodeText: function(node) {
157
159
  return node.textContent;
@@ -708,9 +710,9 @@
708
710
  function parseOperator(node) {
709
711
  let it = NodeTool.getNodeText(node).trim();
710
712
 
711
- // Special case for arrow (→)
713
+ // Special case for arrow (→) with proper spacing
712
714
  if (it === "→") {
713
- return "\\rightarrow";
715
+ return " \\rightarrow "; // Add spaces around arrow
714
716
  }
715
717
 
716
718
  it = MathSymbol.parseOperator(it);
@@ -837,7 +839,28 @@
837
839
  const nodeName = NodeTool.getNodeName(node);
838
840
  switch(nodeName){
839
841
  case 'msub':
840
- render = getRender_default("@1_{@2}");
842
+ render = function(node, children) {
843
+ if (!children || children.length < 2) return '';
844
+
845
+ const base = parse(children[0]);
846
+ if (!base) return '';
847
+
848
+ const sub = children[1];
849
+ if (!sub) return base;
850
+
851
+ // Handle nested subscript with empty mrow
852
+ if (NodeTool.getNodeName(sub) === 'msub' &&
853
+ sub.firstChild &&
854
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
855
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
856
+ const lastChild = sub.lastChild;
857
+ if (!lastChild) return base;
858
+ return `${base}_${parse(lastChild)}`;
859
+ }
860
+
861
+ // Regular subscript
862
+ return `${base}_{${parse(sub)}}`;
863
+ };
841
864
  break;
842
865
  case 'msup':
843
866
  render = getRender_default("@1^{@2}");
@@ -899,10 +922,32 @@
899
922
 
900
923
  function renderTable(node, children) {
901
924
  const template = "\\begin{array}{l}@content\\end{array}";
902
- const render = getRender_joinSeparator(template, "\\\\");
925
+ // Remove extra backslash and add proper spacing
926
+ const render = getRender_joinSeparator(template, " \\\\ ");
903
927
  return render(node, children);
904
928
  }
905
929
 
930
+ function renderMover(node, children) {
931
+ // Check if children exists and has enough elements
932
+ if (!children || children.length < 2) return '';
933
+
934
+ const baseNode = children[0];
935
+ const overNode = children[1];
936
+
937
+ // Check if nodes exist before accessing
938
+ if (!baseNode || !overNode) return '';
939
+
940
+ const overText = NodeTool.getNodeText(overNode)?.trim() || '';
941
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
942
+
943
+ // Handle vector notation
944
+ if (overText === "→" && isAccent) {
945
+ return `\\vec{${parse(baseNode)}}`;
946
+ }
947
+
948
+ return parse(baseNode);
949
+ }
950
+
906
951
  function renderMfenced(node, children) {
907
952
  const [open, close, separatorsStr] = [
908
953
  NodeTool.getAttr(node, 'open', '('),
@@ -913,9 +958,10 @@
913
958
  const parts = renderChildren(children);
914
959
  const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
915
960
 
916
- // Handle empty open/close brackets
917
- if (open === '' && close === '|') {
918
- return `|${content}|`;
961
+ // Handle system of equations with curly brace
962
+ if (open === '{') {
963
+ // If no closing brace specified, use \right.
964
+ return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
919
965
  }
920
966
 
921
967
  // Handle absolute value notation
@@ -923,12 +969,7 @@
923
969
  return `\\left|${content}\\right|`;
924
970
  }
925
971
 
926
- // Regular parentheses
927
- if (open === '(' && close === ')') {
928
- return `\\left(${content}\\right)`;
929
- }
930
-
931
- // Default case
972
+ // Regular cases with proper closing
932
973
  return `\\left${open}${content}\\right${close || '.'}`;
933
974
  }
934
975
 
@@ -1007,62 +1048,6 @@
1007
1048
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1008
1049
  }
1009
1050
 
1010
- function renderMover(node, children) {
1011
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1012
- let result = undefined;
1013
-
1014
- // Get the base node and check if it's a subscript or mrow
1015
- const baseNode = children[0];
1016
- const overNode = children[1];
1017
- const nodeName = NodeTool.getNodeName(baseNode);
1018
- const isSubscript = nodeName === 'msub';
1019
- const isMrow = nodeName === 'mrow';
1020
-
1021
- // Handle case where the base is an arrow and mrow is above
1022
- const baseText = NodeTool.getNodeText(baseNode).trim();
1023
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1024
- return "\\rightarrow";
1025
- }
1026
-
1027
- if (isSubscript) {
1028
- // Handle case like n₂ with arrow
1029
- const base = parse(baseNode);
1030
- return `\\overrightarrow{${base}}`;
1031
- }
1032
-
1033
- if (isMrow) {
1034
- // Handle case like 0 with arrow
1035
- const base = parse(baseNode);
1036
- const overText = NodeTool.getNodeText(overNode).trim();
1037
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1038
-
1039
- if (overText === "→" && isAccent) {
1040
- return `\\overrightarrow{${base}}`;
1041
- }
1042
- }
1043
-
1044
- // Handle case where there is only an arrow with no base
1045
- const overText = NodeTool.getNodeText(overNode).trim();
1046
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1047
- return `\\rightarrow`; // Return just the arrow
1048
- }
1049
-
1050
- for (let i = 0; i < nodes.length - 1; i++) {
1051
- if (!result) {
1052
- result = parse(nodes[i]);
1053
- }
1054
-
1055
- const _overNode = nodes[i + 1];
1056
- const overText = NodeTool.getNodeText(_overNode).trim();
1057
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1058
-
1059
- if (overText === "→" && isAccent) {
1060
- return `\\overrightarrow{${result}}`;
1061
- }
1062
- }
1063
- return result;
1064
- }
1065
-
1066
1051
  function renderMunder(node, children){
1067
1052
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1068
1053
  let result = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ed-mathml2tex",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Convert mathml to latex.",
5
5
  "author": "Mika",
6
6
  "license": "MIT",