ed-mathml2tex 0.0.7 → 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,25 +918,55 @@ 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
- NodeTool.getAttr(node, 'open', '{'),
905
- NodeTool.getAttr(node, 'close', '}'),
906
- NodeTool.getAttr(node, 'separators', '|')
949
+ NodeTool.getAttr(node, 'open', '('),
950
+ NodeTool.getAttr(node, 'close', ')'),
951
+ NodeTool.getAttr(node, 'separators', ',')
907
952
  ];
908
953
 
909
954
  const parts = renderChildren(children);
910
- const content = parts.join(separatorsStr).trim();
955
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
911
956
 
912
- if (open === '{' && close === '') {
913
- return `\\left\\${open}${content}\\right.`;
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.'}`;
914
961
  }
915
962
 
916
- return `\\left\\${open}${content}\\right\\${close}`;
963
+ // Handle absolute value notation
964
+ if (open === '|' && (close === '|' || close === '')) {
965
+ return `\\left|${content}\\right|`;
966
+ }
967
+
968
+ // Regular cases with proper closing
969
+ return `\\left${open}${content}\\right${close || '.'}`;
917
970
  }
918
971
 
919
972
  function renderMfrac(node, children){
@@ -933,9 +986,9 @@ function renderMfrac(node, children){
933
986
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
934
987
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
935
988
  ) {
936
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
989
+ render = getRender_default("\\binom{@1}{@2}");
937
990
  } else {
938
- render = getRender_default("{}_{@2}^{@1}");
991
+ render = getRender_default("\\frac{@1}{@2}");
939
992
  }
940
993
  } else {
941
994
  render = getRender_default("\\frac{@1}{@2}");
@@ -991,62 +1044,6 @@ function renderMmultiscripts(node, children) {
991
1044
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
992
1045
  }
993
1046
 
994
- function renderMover(node, children) {
995
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
996
- let result = undefined;
997
-
998
- // Get the base node and check if it's a subscript or mrow
999
- const baseNode = children[0];
1000
- const overNode = children[1];
1001
- const nodeName = NodeTool.getNodeName(baseNode);
1002
- const isSubscript = nodeName === 'msub';
1003
- const isMrow = nodeName === 'mrow';
1004
-
1005
- // Handle case where the base is an arrow and mrow is above
1006
- const baseText = NodeTool.getNodeText(baseNode).trim();
1007
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1008
- return "\\rightarrow";
1009
- }
1010
-
1011
- if (isSubscript) {
1012
- // Handle case like n₂ with arrow
1013
- const base = parse(baseNode);
1014
- return `\\overrightarrow{${base}}`;
1015
- }
1016
-
1017
- if (isMrow) {
1018
- // Handle case like 0 with arrow
1019
- const base = parse(baseNode);
1020
- const overText = NodeTool.getNodeText(overNode).trim();
1021
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1022
-
1023
- if (overText === "→" && isAccent) {
1024
- return `\\overrightarrow{${base}}`;
1025
- }
1026
- }
1027
-
1028
- // Handle case where there is only an arrow with no base
1029
- const overText = NodeTool.getNodeText(overNode).trim();
1030
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1031
- return `\\rightarrow`; // Return just the arrow
1032
- }
1033
-
1034
- for (let i = 0; i < nodes.length - 1; i++) {
1035
- if (!result) {
1036
- result = parse(nodes[i]);
1037
- }
1038
-
1039
- const _overNode = nodes[i + 1];
1040
- const overText = NodeTool.getNodeText(_overNode).trim();
1041
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1042
-
1043
- if (overText === "→" && isAccent) {
1044
- return `\\overrightarrow{${result}}`;
1045
- }
1046
- }
1047
- return result;
1048
- }
1049
-
1050
1047
  function renderMunder(node, children){
1051
1048
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1052
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,25 +916,55 @@ 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
- NodeTool.getAttr(node, 'open', '{'),
903
- NodeTool.getAttr(node, 'close', '}'),
904
- NodeTool.getAttr(node, 'separators', '|')
947
+ NodeTool.getAttr(node, 'open', '('),
948
+ NodeTool.getAttr(node, 'close', ')'),
949
+ NodeTool.getAttr(node, 'separators', ',')
905
950
  ];
906
951
 
907
952
  const parts = renderChildren(children);
908
- const content = parts.join(separatorsStr).trim();
953
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
909
954
 
910
- if (open === '{' && close === '') {
911
- return `\\left\\${open}${content}\\right.`;
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.'}`;
912
959
  }
913
960
 
914
- return `\\left\\${open}${content}\\right\\${close}`;
961
+ // Handle absolute value notation
962
+ if (open === '|' && (close === '|' || close === '')) {
963
+ return `\\left|${content}\\right|`;
964
+ }
965
+
966
+ // Regular cases with proper closing
967
+ return `\\left${open}${content}\\right${close || '.'}`;
915
968
  }
916
969
 
917
970
  function renderMfrac(node, children){
@@ -931,9 +984,9 @@ function renderMfrac(node, children){
931
984
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
932
985
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
933
986
  ) {
934
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
987
+ render = getRender_default("\\binom{@1}{@2}");
935
988
  } else {
936
- render = getRender_default("{}_{@2}^{@1}");
989
+ render = getRender_default("\\frac{@1}{@2}");
937
990
  }
938
991
  } else {
939
992
  render = getRender_default("\\frac{@1}{@2}");
@@ -989,62 +1042,6 @@ function renderMmultiscripts(node, children) {
989
1042
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
990
1043
  }
991
1044
 
992
- function renderMover(node, children) {
993
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
994
- let result = undefined;
995
-
996
- // Get the base node and check if it's a subscript or mrow
997
- const baseNode = children[0];
998
- const overNode = children[1];
999
- const nodeName = NodeTool.getNodeName(baseNode);
1000
- const isSubscript = nodeName === 'msub';
1001
- const isMrow = nodeName === 'mrow';
1002
-
1003
- // Handle case where the base is an arrow and mrow is above
1004
- const baseText = NodeTool.getNodeText(baseNode).trim();
1005
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1006
- return "\\rightarrow";
1007
- }
1008
-
1009
- if (isSubscript) {
1010
- // Handle case like n₂ with arrow
1011
- const base = parse(baseNode);
1012
- return `\\overrightarrow{${base}}`;
1013
- }
1014
-
1015
- if (isMrow) {
1016
- // Handle case like 0 with arrow
1017
- const base = parse(baseNode);
1018
- const overText = NodeTool.getNodeText(overNode).trim();
1019
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1020
-
1021
- if (overText === "→" && isAccent) {
1022
- return `\\overrightarrow{${base}}`;
1023
- }
1024
- }
1025
-
1026
- // Handle case where there is only an arrow with no base
1027
- const overText = NodeTool.getNodeText(overNode).trim();
1028
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1029
- return `\\rightarrow`; // Return just the arrow
1030
- }
1031
-
1032
- for (let i = 0; i < nodes.length - 1; i++) {
1033
- if (!result) {
1034
- result = parse(nodes[i]);
1035
- }
1036
-
1037
- const _overNode = nodes[i + 1];
1038
- const overText = NodeTool.getNodeText(_overNode).trim();
1039
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1040
-
1041
- if (overText === "→" && isAccent) {
1042
- return `\\overrightarrow{${result}}`;
1043
- }
1044
- }
1045
- return result;
1046
- }
1047
-
1048
1045
  function renderMunder(node, children){
1049
1046
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1050
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,25 +922,55 @@
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
- NodeTool.getAttr(node, 'open', '{'),
909
- NodeTool.getAttr(node, 'close', '}'),
910
- NodeTool.getAttr(node, 'separators', '|')
953
+ NodeTool.getAttr(node, 'open', '('),
954
+ NodeTool.getAttr(node, 'close', ')'),
955
+ NodeTool.getAttr(node, 'separators', ',')
911
956
  ];
912
957
 
913
958
  const parts = renderChildren(children);
914
- const content = parts.join(separatorsStr).trim();
959
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
915
960
 
916
- if (open === '{' && close === '') {
917
- return `\\left\\${open}${content}\\right.`;
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.'}`;
918
965
  }
919
966
 
920
- return `\\left\\${open}${content}\\right\\${close}`;
967
+ // Handle absolute value notation
968
+ if (open === '|' && (close === '|' || close === '')) {
969
+ return `\\left|${content}\\right|`;
970
+ }
971
+
972
+ // Regular cases with proper closing
973
+ return `\\left${open}${content}\\right${close || '.'}`;
921
974
  }
922
975
 
923
976
  function renderMfrac(node, children){
@@ -937,9 +990,9 @@
937
990
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
938
991
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
939
992
  ) {
940
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
993
+ render = getRender_default("\\binom{@1}{@2}");
941
994
  } else {
942
- render = getRender_default("{}_{@2}^{@1}");
995
+ render = getRender_default("\\frac{@1}{@2}");
943
996
  }
944
997
  } else {
945
998
  render = getRender_default("\\frac{@1}{@2}");
@@ -995,62 +1048,6 @@
995
1048
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
996
1049
  }
997
1050
 
998
- function renderMover(node, children) {
999
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1000
- let result = undefined;
1001
-
1002
- // Get the base node and check if it's a subscript or mrow
1003
- const baseNode = children[0];
1004
- const overNode = children[1];
1005
- const nodeName = NodeTool.getNodeName(baseNode);
1006
- const isSubscript = nodeName === 'msub';
1007
- const isMrow = nodeName === 'mrow';
1008
-
1009
- // Handle case where the base is an arrow and mrow is above
1010
- const baseText = NodeTool.getNodeText(baseNode).trim();
1011
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1012
- return "\\rightarrow";
1013
- }
1014
-
1015
- if (isSubscript) {
1016
- // Handle case like n₂ with arrow
1017
- const base = parse(baseNode);
1018
- return `\\overrightarrow{${base}}`;
1019
- }
1020
-
1021
- if (isMrow) {
1022
- // Handle case like 0 with arrow
1023
- const base = parse(baseNode);
1024
- const overText = NodeTool.getNodeText(overNode).trim();
1025
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1026
-
1027
- if (overText === "→" && isAccent) {
1028
- return `\\overrightarrow{${base}}`;
1029
- }
1030
- }
1031
-
1032
- // Handle case where there is only an arrow with no base
1033
- const overText = NodeTool.getNodeText(overNode).trim();
1034
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1035
- return `\\rightarrow`; // Return just the arrow
1036
- }
1037
-
1038
- for (let i = 0; i < nodes.length - 1; i++) {
1039
- if (!result) {
1040
- result = parse(nodes[i]);
1041
- }
1042
-
1043
- const _overNode = nodes[i + 1];
1044
- const overText = NodeTool.getNodeText(_overNode).trim();
1045
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1046
-
1047
- if (overText === "→" && isAccent) {
1048
- return `\\overrightarrow{${result}}`;
1049
- }
1050
- }
1051
- return result;
1052
- }
1053
-
1054
1051
  function renderMunder(node, children){
1055
1052
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1056
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,25 +918,55 @@ 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
- NodeTool.getAttr(node, 'open', '{'),
905
- NodeTool.getAttr(node, 'close', '}'),
906
- NodeTool.getAttr(node, 'separators', '|')
949
+ NodeTool.getAttr(node, 'open', '('),
950
+ NodeTool.getAttr(node, 'close', ')'),
951
+ NodeTool.getAttr(node, 'separators', ',')
907
952
  ];
908
953
 
909
954
  const parts = renderChildren(children);
910
- const content = parts.join(separatorsStr).trim();
955
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
911
956
 
912
- if (open === '{' && close === '') {
913
- return `\\left\\${open}${content}\\right.`;
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.'}`;
914
961
  }
915
962
 
916
- return `\\left\\${open}${content}\\right\\${close}`;
963
+ // Handle absolute value notation
964
+ if (open === '|' && (close === '|' || close === '')) {
965
+ return `\\left|${content}\\right|`;
966
+ }
967
+
968
+ // Regular cases with proper closing
969
+ return `\\left${open}${content}\\right${close || '.'}`;
917
970
  }
918
971
 
919
972
  function renderMfrac(node, children){
@@ -933,9 +986,9 @@ function renderMfrac(node, children){
933
986
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
934
987
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
935
988
  ) {
936
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
989
+ render = getRender_default("\\binom{@1}{@2}");
937
990
  } else {
938
- render = getRender_default("{}_{@2}^{@1}");
991
+ render = getRender_default("\\frac{@1}{@2}");
939
992
  }
940
993
  } else {
941
994
  render = getRender_default("\\frac{@1}{@2}");
@@ -991,62 +1044,6 @@ function renderMmultiscripts(node, children) {
991
1044
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
992
1045
  }
993
1046
 
994
- function renderMover(node, children) {
995
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
996
- let result = undefined;
997
-
998
- // Get the base node and check if it's a subscript or mrow
999
- const baseNode = children[0];
1000
- const overNode = children[1];
1001
- const nodeName = NodeTool.getNodeName(baseNode);
1002
- const isSubscript = nodeName === 'msub';
1003
- const isMrow = nodeName === 'mrow';
1004
-
1005
- // Handle case where the base is an arrow and mrow is above
1006
- const baseText = NodeTool.getNodeText(baseNode).trim();
1007
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1008
- return "\\rightarrow";
1009
- }
1010
-
1011
- if (isSubscript) {
1012
- // Handle case like n₂ with arrow
1013
- const base = parse(baseNode);
1014
- return `\\overrightarrow{${base}}`;
1015
- }
1016
-
1017
- if (isMrow) {
1018
- // Handle case like 0 with arrow
1019
- const base = parse(baseNode);
1020
- const overText = NodeTool.getNodeText(overNode).trim();
1021
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1022
-
1023
- if (overText === "→" && isAccent) {
1024
- return `\\overrightarrow{${base}}`;
1025
- }
1026
- }
1027
-
1028
- // Handle case where there is only an arrow with no base
1029
- const overText = NodeTool.getNodeText(overNode).trim();
1030
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1031
- return `\\rightarrow`; // Return just the arrow
1032
- }
1033
-
1034
- for (let i = 0; i < nodes.length - 1; i++) {
1035
- if (!result) {
1036
- result = parse(nodes[i]);
1037
- }
1038
-
1039
- const _overNode = nodes[i + 1];
1040
- const overText = NodeTool.getNodeText(_overNode).trim();
1041
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1042
-
1043
- if (overText === "→" && isAccent) {
1044
- return `\\overrightarrow{${result}}`;
1045
- }
1046
- }
1047
- return result;
1048
- }
1049
-
1050
1047
  function renderMunder(node, children){
1051
1048
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1052
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,25 +916,55 @@ 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
- NodeTool.getAttr(node, 'open', '{'),
903
- NodeTool.getAttr(node, 'close', '}'),
904
- NodeTool.getAttr(node, 'separators', '|')
947
+ NodeTool.getAttr(node, 'open', '('),
948
+ NodeTool.getAttr(node, 'close', ')'),
949
+ NodeTool.getAttr(node, 'separators', ',')
905
950
  ];
906
951
 
907
952
  const parts = renderChildren(children);
908
- const content = parts.join(separatorsStr).trim();
953
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
909
954
 
910
- if (open === '{' && close === '') {
911
- return `\\left\\${open}${content}\\right.`;
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.'}`;
912
959
  }
913
960
 
914
- return `\\left\\${open}${content}\\right\\${close}`;
961
+ // Handle absolute value notation
962
+ if (open === '|' && (close === '|' || close === '')) {
963
+ return `\\left|${content}\\right|`;
964
+ }
965
+
966
+ // Regular cases with proper closing
967
+ return `\\left${open}${content}\\right${close || '.'}`;
915
968
  }
916
969
 
917
970
  function renderMfrac(node, children){
@@ -931,9 +984,9 @@ function renderMfrac(node, children){
931
984
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
932
985
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
933
986
  ) {
934
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
987
+ render = getRender_default("\\binom{@1}{@2}");
935
988
  } else {
936
- render = getRender_default("{}_{@2}^{@1}");
989
+ render = getRender_default("\\frac{@1}{@2}");
937
990
  }
938
991
  } else {
939
992
  render = getRender_default("\\frac{@1}{@2}");
@@ -989,62 +1042,6 @@ function renderMmultiscripts(node, children) {
989
1042
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
990
1043
  }
991
1044
 
992
- function renderMover(node, children) {
993
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
994
- let result = undefined;
995
-
996
- // Get the base node and check if it's a subscript or mrow
997
- const baseNode = children[0];
998
- const overNode = children[1];
999
- const nodeName = NodeTool.getNodeName(baseNode);
1000
- const isSubscript = nodeName === 'msub';
1001
- const isMrow = nodeName === 'mrow';
1002
-
1003
- // Handle case where the base is an arrow and mrow is above
1004
- const baseText = NodeTool.getNodeText(baseNode).trim();
1005
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1006
- return "\\rightarrow";
1007
- }
1008
-
1009
- if (isSubscript) {
1010
- // Handle case like n₂ with arrow
1011
- const base = parse(baseNode);
1012
- return `\\overrightarrow{${base}}`;
1013
- }
1014
-
1015
- if (isMrow) {
1016
- // Handle case like 0 with arrow
1017
- const base = parse(baseNode);
1018
- const overText = NodeTool.getNodeText(overNode).trim();
1019
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1020
-
1021
- if (overText === "→" && isAccent) {
1022
- return `\\overrightarrow{${base}}`;
1023
- }
1024
- }
1025
-
1026
- // Handle case where there is only an arrow with no base
1027
- const overText = NodeTool.getNodeText(overNode).trim();
1028
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1029
- return `\\rightarrow`; // Return just the arrow
1030
- }
1031
-
1032
- for (let i = 0; i < nodes.length - 1; i++) {
1033
- if (!result) {
1034
- result = parse(nodes[i]);
1035
- }
1036
-
1037
- const _overNode = nodes[i + 1];
1038
- const overText = NodeTool.getNodeText(_overNode).trim();
1039
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1040
-
1041
- if (overText === "→" && isAccent) {
1042
- return `\\overrightarrow{${result}}`;
1043
- }
1044
- }
1045
- return result;
1046
- }
1047
-
1048
1045
  function renderMunder(node, children){
1049
1046
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1050
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,25 +922,55 @@
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
- NodeTool.getAttr(node, 'open', '{'),
909
- NodeTool.getAttr(node, 'close', '}'),
910
- NodeTool.getAttr(node, 'separators', '|')
953
+ NodeTool.getAttr(node, 'open', '('),
954
+ NodeTool.getAttr(node, 'close', ')'),
955
+ NodeTool.getAttr(node, 'separators', ',')
911
956
  ];
912
957
 
913
958
  const parts = renderChildren(children);
914
- const content = parts.join(separatorsStr).trim();
959
+ const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
915
960
 
916
- if (open === '{' && close === '') {
917
- return `\\left\\${open}${content}\\right.`;
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.'}`;
918
965
  }
919
966
 
920
- return `\\left\\${open}${content}\\right\\${close}`;
967
+ // Handle absolute value notation
968
+ if (open === '|' && (close === '|' || close === '')) {
969
+ return `\\left|${content}\\right|`;
970
+ }
971
+
972
+ // Regular cases with proper closing
973
+ return `\\left${open}${content}\\right${close || '.'}`;
921
974
  }
922
975
 
923
976
  function renderMfrac(node, children){
@@ -937,9 +990,9 @@
937
990
  if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
938
991
  (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
939
992
  ) {
940
- render = getRender_default("\\DELETE_BRACKET_L\\binom{@1}{@2}\\DELETE_BRACKET_R");
993
+ render = getRender_default("\\binom{@1}{@2}");
941
994
  } else {
942
- render = getRender_default("{}_{@2}^{@1}");
995
+ render = getRender_default("\\frac{@1}{@2}");
943
996
  }
944
997
  } else {
945
998
  render = getRender_default("\\frac{@1}{@2}");
@@ -995,62 +1048,6 @@
995
1048
  return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
996
1049
  }
997
1050
 
998
- function renderMover(node, children) {
999
- const nodes = flattenNodeTreeByNodeName(node, 'mover');
1000
- let result = undefined;
1001
-
1002
- // Get the base node and check if it's a subscript or mrow
1003
- const baseNode = children[0];
1004
- const overNode = children[1];
1005
- const nodeName = NodeTool.getNodeName(baseNode);
1006
- const isSubscript = nodeName === 'msub';
1007
- const isMrow = nodeName === 'mrow';
1008
-
1009
- // Handle case where the base is an arrow and mrow is above
1010
- const baseText = NodeTool.getNodeText(baseNode).trim();
1011
- if (baseText === "→" && NodeTool.getNodeName(overNode) === "mrow") {
1012
- return "\\rightarrow";
1013
- }
1014
-
1015
- if (isSubscript) {
1016
- // Handle case like n₂ with arrow
1017
- const base = parse(baseNode);
1018
- return `\\overrightarrow{${base}}`;
1019
- }
1020
-
1021
- if (isMrow) {
1022
- // Handle case like 0 with arrow
1023
- const base = parse(baseNode);
1024
- const overText = NodeTool.getNodeText(overNode).trim();
1025
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1026
-
1027
- if (overText === "→" && isAccent) {
1028
- return `\\overrightarrow{${base}}`;
1029
- }
1030
- }
1031
-
1032
- // Handle case where there is only an arrow with no base
1033
- const overText = NodeTool.getNodeText(overNode).trim();
1034
- if (overText === "→" && NodeTool.getNodeText(baseNode).trim() === "") {
1035
- return `\\rightarrow`; // Return just the arrow
1036
- }
1037
-
1038
- for (let i = 0; i < nodes.length - 1; i++) {
1039
- if (!result) {
1040
- result = parse(nodes[i]);
1041
- }
1042
-
1043
- const _overNode = nodes[i + 1];
1044
- const overText = NodeTool.getNodeText(_overNode).trim();
1045
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1046
-
1047
- if (overText === "→" && isAccent) {
1048
- return `\\overrightarrow{${result}}`;
1049
- }
1050
- }
1051
- return result;
1052
- }
1053
-
1054
1051
  function renderMunder(node, children){
1055
1052
  const nodes = flattenNodeTreeByNodeName(node, 'munder');
1056
1053
  let result = undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ed-mathml2tex",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Convert mathml to latex.",
5
5
  "author": "Mika",
6
6
  "license": "MIT",