ed-mathml2tex 0.1.0 → 0.1.1

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.
@@ -588,74 +588,75 @@
588
588
  }
589
589
  };
590
590
 
591
- function getRender_default(template) {
592
- return function(node, children) {
593
- const parts = renderChildren(children);
594
- return renderTemplate(template, parts);
595
- };
596
- }
597
-
598
591
  function getRender_joinSeparator(template, separator = '') {
599
- return function(node, children) {
592
+ return function (node, children) {
600
593
  const parts = renderChildren(children);
601
594
  return template.replace('@content', parts.join(separator));
602
595
  };
603
596
  }
604
597
 
605
- function convert(mathmlHtml){
598
+ function convert(mathmlHtml) {
606
599
  const math = NodeTool.parseMath(mathmlHtml);
607
600
  let result = toLatex(parse(math));
608
-
609
- // Last-chance post-processing for specific patterns
610
- if (mathmlHtml.includes("<munder>") &&
611
- mathmlHtml.includes("<mo>→</mo>") &&
612
- mathmlHtml.includes("<mrow/>")) {
601
+
602
+ // Xử sau cuối cho các mẫu đặc biệt
603
+ result = result.replace(/\\right\.\./g, "\\right."); // Loại bỏ dấu chấm trùng lặp
604
+ result = result.replace(/\.\s*\./g, "."); // Loại bỏ dấu chấm trùng lặp
605
+
606
+ if (mathmlHtml.includes("<munder>") &&
607
+ mathmlHtml.includes("<mo>→</mo>") &&
608
+ mathmlHtml.includes("<mrow/>")) {
613
609
  if (result.includes("\\limits")) {
614
610
  result = "\\underset{}{\\rightarrow}";
615
611
  }
616
612
  }
617
-
613
+
614
+ // Thêm xử lý cho các thẻ MathML khác
615
+ result = result
616
+ .replace(/∞/g, "\\infty") // Vô cực
617
+ .replace(/∑/g, "\\sum") // Tổng
618
+ .replace(/∏/g, "\\prod") // Tích
619
+ .replace(/∫/g, "\\int"); // Tích phân
620
+
618
621
  return result;
619
622
  }
620
623
 
621
624
  function toLatex(result) {
622
- // binomial coefficients
625
+ // Xử lý binomial coefficients
623
626
  result = result.replace(/\\left\(\\DELETE_BRACKET_L/g, '');
624
627
  result = result.replace(/\\DELETE_BRACKET_R\\right\)/g, '');
625
628
  result = result.replace(/\\DELETE_BRACKET_L/g, '');
626
629
  result = result.replace(/\\DELETE_BRACKET_R/g, '');
627
-
628
- // Fix all cases of arrows with limits
629
- // Case 1: munder - arrow with empty subscript
630
+
631
+ // Xử các trường hợp mũi tên với giới hạn
630
632
  result = result.replace(/→\\limits_{}/g, "\\underset{}{\\rightarrow}");
631
633
  result = result.replace(/→\\limits_{(\s*)}/g, "\\underset{}{\\rightarrow}");
632
634
  result = result.replace(/\\rightarrow\\limits_{}/g, "\\underset{}{\\rightarrow}");
633
635
  result = result.replace(/\\rightarrow\\limits_{(\s*)}/g, "\\underset{}{\\rightarrow}");
634
-
635
- // Case 2: munder - arrow with non-empty subscript
636
+
636
637
  result = result.replace(/→\\limits_\{([^}]*)\}/g, "\\underset{$1}{\\rightarrow}");
637
638
  result = result.replace(/\\rightarrow\\limits_\{([^}]*)\}/g, "\\underset{$1}{\\rightarrow}");
638
-
639
- // Case 3: munderover - arrow with both subscript and superscript
639
+
640
640
  result = result.replace(/→\\limits_\{([^}]*)\}\^\{([^}]*)\}/g, "\\overset{$2}{\\underset{$1}{\\rightarrow}}");
641
641
  result = result.replace(/\\rightarrow\\limits_\{([^}]*)\}\^\{([^}]*)\}/g, "\\overset{$2}{\\underset{$1}{\\rightarrow}}");
642
-
643
- // Case 4: mover - fix expressions with arrow superscript
644
- // Simple expression with arrow superscript: expr^{\rightarrow} → \overrightarrow{expr}
642
+
643
+ // Xử vector các hiệu đặc biệt
645
644
  result = result.replace(/([^{}\s]+)\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
646
645
  result = result.replace(/\{([^{}]+)\}\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
647
-
648
- // Complex expressions with subscripts and arrow: expr_{sub}^{\rightarrow} → \overrightarrow{expr_{sub}}
649
646
  result = result.replace(/([A-Za-z0-9]+)_\{([^{}]+)\}\^\{\\rightarrow\}/g, "\\overrightarrow{$1_{$2}}");
650
647
  result = result.replace(/([A-Za-z0-9]+)_([0-9])\^\{\\rightarrow\}/g, "\\overrightarrow{$1_$2}");
651
-
652
- // Very complex expressions: (expr)^{\rightarrow} → \overrightarrow{(expr)}
653
648
  result = result.replace(/(\([^()]+\))\^\{\\rightarrow\}/g, "\\overrightarrow{$1}");
654
-
655
- // Also match if there are spaces
656
- result = result.replace(/→\s*\\limits\s*_\s*{\s*}/g, "\\underset{}{\\rightarrow}");
657
- result = result.replace(/\\rightarrow\s*\\limits\s*_\s*{\s*}/g, "\\underset{}{\\rightarrow}");
658
-
649
+
650
+ // Thêm xử các hiệu toán học phổ biến
651
+ result = result.replace(/≤/g, "\\leq");
652
+ result = result.replace(/≥/g, "\\geq");
653
+ result = result.replace(/≠/g, "\\neq");
654
+ result = result.replace(/≈/g, "\\approx");
655
+ result = result.replace(/π/g, "\\pi");
656
+ result = result.replace(/α/g, "\\alpha");
657
+ result = result.replace(/β/g, "\\beta");
658
+ result = result.replace(/γ/g, "\\gamma");
659
+
659
660
  return result;
660
661
  }
661
662
 
@@ -669,15 +670,15 @@
669
670
  }
670
671
 
671
672
  // @see https://www.w3.org/TR/MathML3/chapter7.html
673
+ // Cải tiến parseLeaf để hỗ trợ thêm các ký hiệu
672
674
  function parseLeaf(node) {
673
675
  let r = '';
674
676
  const nodeName = NodeTool.getNodeName(node);
675
-
676
- // Special case for empty mrow
677
+
677
678
  if (nodeName === "mrow" && NodeTool.getNodeText(node).trim() === "") {
678
679
  return "";
679
680
  }
680
-
681
+
681
682
  switch (nodeName) {
682
683
  case 'mi':
683
684
  r = parseElementMi(node);
@@ -688,53 +689,76 @@
688
689
  case 'mo':
689
690
  r = parseOperator(node);
690
691
  break;
691
- case 'ms': r = parseElementMs(node);
692
+ case 'ms':
693
+ r = parseElementMs(node);
692
694
  break;
693
- case 'mtext': r = parseElementMtext(node);
695
+ case 'mtext':
696
+ r = parseElementMtext(node);
694
697
  break;
695
- case 'mglyph': r = parseElementMglyph(node);
698
+ case 'mglyph':
699
+ r = parseElementMglyph(node);
696
700
  break;
697
- case 'mprescripts': r = '';
701
+ case 'mprescripts':
702
+ r = '';
698
703
  break;
699
- case 'mspace': r = parseElementMspace();
700
- case 'none': r = '\\:';
701
- //TODO other usecase of 'none' ?
704
+ case 'mspace':
705
+ r = parseElementMspace();
702
706
  break;
703
- default: r = escapeSpecialChars(NodeTool.getNodeText(node).trim());
707
+ case 'none':
708
+ r = '\\:';
709
+ break;
710
+ default:
711
+ r = escapeSpecialChars(NodeTool.getNodeText(node).trim());
704
712
  break;
705
713
  }
706
714
  return r;
707
715
  }
708
716
 
709
- // operator token, mathematical operators
717
+ // Cải tiến parseOperator để hỗ trợ thêm toán tử
710
718
  function parseOperator(node) {
711
719
  let it = NodeTool.getNodeText(node).trim();
712
-
713
- // Special case for arrow (→) with proper spacing
714
- if (it === "→") {
715
- return " \\rightarrow "; // Add spaces around arrow
716
- }
717
-
718
- it = MathSymbol.parseOperator(it);
719
- return escapeSpecialChars(it);
720
+
721
+ const operatorMap = {
722
+ "→": " \\rightarrow ",
723
+ "←": " \\leftarrow ",
724
+ "↔": " \\leftrightarrow ",
725
+ "⇒": " \\Rightarrow ",
726
+ "⇐": " \\Leftarrow ",
727
+ "⇔": " \\Leftrightarrow ",
728
+ "±": " \\pm ",
729
+ "×": " \\times ",
730
+ "÷": " \\div ",
731
+ "∑": " \\sum ",
732
+ "∏": " \\prod ",
733
+ "∫": " \\int ",
734
+ "−": "-",
735
+ "≠": " \\neq ",
736
+ ">": " > ",
737
+ "=": " = ",
738
+ ",": ", ", // Dấu phẩy trong tập hợp
739
+ ";": ";", // Dấu chấm phẩy không cần khoảng trắng
740
+ "Ω": "\\Omega" // Thêm ký hiệu Omega
741
+ };
742
+
743
+ return operatorMap[it] || escapeSpecialChars(MathSymbol.parseOperator(it));
720
744
  }
721
745
 
722
746
  // Math identifier
723
747
  function parseElementMi(node) {
724
748
  let it = NodeTool.getNodeText(node).trim();
725
-
749
+
726
750
  // Handle vectors (e.g. AB', AI)
727
751
  if (it.includes("'")) {
728
752
  return it; // Return as is to handle in mrow
729
753
  }
730
-
754
+
731
755
  // Handle subscripts (e.g. n₂)
732
- if (it.match(/[a-zA-Z]\d/)) {
756
+ if (it.match(/[a-zA-z]\d/)) {
733
757
  const base = it[0];
734
758
  const sub = it[1];
735
759
  return `${base}_{${sub}}`;
736
760
  }
737
-
761
+
738
762
  it = MathSymbol.parseIdentifier(it);
739
763
  return escapeSpecialChars(it);
740
764
  }
@@ -746,33 +770,33 @@
746
770
  }
747
771
 
748
772
  // Math String
749
- function parseElementMs(node){
773
+ function parseElementMs(node) {
750
774
  const content = NodeTool.getNodeText(node).trimRight();
751
775
  const it = escapeSpecialChars(content);
752
776
  return ['"', it, '"'].join('');
753
777
  }
754
778
 
755
779
  // Math Text
756
- function parseElementMtext(node){
780
+ function parseElementMtext(node) {
757
781
  const content = NodeTool.getNodeText(node);
758
782
  const it = escapeSpecialChars(content);
759
783
  return `\\text{${it}}`;
760
784
  }
761
785
 
762
786
  // Math glyph (image)
763
- function parseElementMglyph(node){
787
+ function parseElementMglyph(node) {
764
788
  const it = ['"', NodeTool.getAttr(node, 'alt', ''), '"'].join('');
765
789
  return escapeSpecialChars(it);
766
790
  }
767
791
 
768
792
  // TODO need or not
769
- function parseElementMspace(node){
793
+ function parseElementMspace(node) {
770
794
  return '';
771
795
  }
772
796
 
773
797
  function escapeSpecialChars(text) {
774
798
  const specialChars = /\$|%|_|&|#|\{|\}/g;
775
- text = text.replace(specialChars, char => `\\${ char }`);
799
+ text = text.replace(specialChars, char => `\\${char}`);
776
800
  return text;
777
801
  }
778
802
 
@@ -790,26 +814,33 @@
790
814
  const parts = [];
791
815
  let lefts = [];
792
816
  Array.prototype.forEach.call(children, (node) => {
793
- if(NodeTool.getNodeName(node) === 'mo'){
817
+ if (NodeTool.getNodeName(node) === 'mo') {
794
818
  const op = NodeTool.getNodeText(node).trim();
795
- if(Brackets.contains(op)){
819
+ if (Brackets.contains(op)) {
796
820
  let stretchy = NodeTool.getAttr(node, 'stretchy', 'true');
797
821
  stretchy = ['', 'true'].indexOf(stretchy) > -1;
798
-
799
- if(Brackets.isRight(op)){
822
+
823
+ if (Brackets.isRight(op)) {
800
824
  const nearLeft = lefts[lefts.length - 1];
801
- if(nearLeft){
802
- if(Brackets.isPair(nearLeft, op)){
825
+ if (nearLeft) {
826
+ if (Brackets.isPair(nearLeft, op)) {
803
827
  parts.push(Brackets.parseRight(op, stretchy));
804
828
  lefts.pop();
805
829
  } else {
806
830
  parts.push(`\\right${op}`);
807
831
  }
808
832
  } else {
809
- parts.push(`\\right${op}`);
833
+ parts.push(op); // Chỉ thêm dấu ngoặc đóng khi đứng một mình
810
834
  }
811
835
  } else {
812
- parts.push(Brackets.parseLeft(op, stretchy));
836
+ // Xử lý dấu ngoặc mở
837
+ if (op === '{' && node.parentNode &&
838
+ Array.from(NodeTool.getChildren(node.parentNode)).some(child =>
839
+ NodeTool.getNodeName(child) === 'mtable')) {
840
+ parts.push('\\left\\{');
841
+ } else {
842
+ parts.push(Brackets.parseLeft(op, stretchy));
843
+ }
813
844
  lefts.push(op);
814
845
  }
815
846
  } else {
@@ -819,351 +850,444 @@
819
850
  parts.push(parse(node));
820
851
  }
821
852
  });
822
- // Only add \right. if there are unmatched left brackets
823
- if(lefts.length > 0 && !parts.some(p => p.includes('\\right'))) {
853
+
854
+ // Chỉ thêm \right. nếu dấu ngoặc mở chưa được đóng
855
+ if (lefts.length > 0 && !parts.some(p => p.includes('\\right'))) {
824
856
  parts.push("\\right.");
825
857
  }
826
858
  lefts = undefined;
827
859
  return parts;
828
860
  }
829
861
 
830
-
831
862
  function getRender(node) {
832
863
  let render = undefined;
833
864
  const nodeName = NodeTool.getNodeName(node);
834
- switch(nodeName){
865
+
866
+ switch (nodeName) {
867
+ case 'mrow':
868
+ render = function (node, children) {
869
+ const childrenArray = Array.from(children);
870
+ if (childrenArray.length >= 2 &&
871
+ NodeTool.getNodeName(childrenArray[0]) === 'mo' &&
872
+ NodeTool.getNodeName(childrenArray[childrenArray.length - 1]) === 'mo') {
873
+ const firstOp = NodeTool.getNodeText(childrenArray[0]).trim();
874
+ const lastOp = NodeTool.getNodeText(childrenArray[childrenArray.length - 1]).trim();
875
+
876
+ // Xử lý đặc biệt cho dấu ngoặc nhọn chứa mtable
877
+ if (firstOp === '{' && childrenArray.some(child =>
878
+ NodeTool.getNodeName(child) === 'mtable')) {
879
+ const innerContent = childrenArray
880
+ .slice(1, -1)
881
+ .map(child => parse(child))
882
+ .join('');
883
+ return `\\left\\{${innerContent}\\right.`;
884
+ }
885
+
886
+ // Xử lý cho trường hợp [a;b) và [a,b)
887
+ if (firstOp === '[' && lastOp === ')') {
888
+ const innerContent = childrenArray
889
+ .slice(1, -1)
890
+ .map(child => parse(child))
891
+ .join('');
892
+ return `\\left[${innerContent}\\right)`;
893
+ }
894
+
895
+ // Xử lý ngoặc nhọn bình thường
896
+ if (firstOp === '{' && lastOp === '}') {
897
+ const innerContent = childrenArray
898
+ .slice(1, -1)
899
+ .map(child => parse(child))
900
+ .join('');
901
+ return `\\{${innerContent}\\}`;
902
+ }
903
+
904
+ // Bỏ qua nếu firstOp là rỗng (trường hợp <mo></mo>)
905
+ if (!firstOp && !lastOp) {
906
+ return getRender_joinSeparator("@content")(node, childrenArray);
907
+ }
908
+
909
+ // Xử lý đặc biệt cho dấu ngoặc vuông chứa mtable
910
+ if (firstOp === '[') {
911
+ const innerContent = childrenArray
912
+ .slice(1, -1) // Bỏ dấu ngoặc mở và đóng
913
+ .map(child => {
914
+ const parsed = parse(child);
915
+ // Nếu child là mtable, trả về nội dung đã được định dạng với \begin{array}{l} ... \end{array}
916
+ if (NodeTool.getNodeName(child) === 'mtable') {
917
+ const rows = Array.from(NodeTool.getChildren(child)).map(row => {
918
+ const rowChildren = Array.from(NodeTool.getChildren(row));
919
+ return rowChildren.map(cell => parse(cell)).join('');
920
+ });
921
+ return `\\begin{array}{l} ${rows.join(' \\\\ ')} \\end{array}`;
922
+ }
923
+ // Nếu child là mrow chứa mtable, xử lý tương tự
924
+ if (NodeTool.getNodeName(child) === 'mrow' &&
925
+ Array.from(NodeTool.getChildren(child)).some(c => NodeTool.getNodeName(c) === 'mtable')) {
926
+ const mtableChild = Array.from(NodeTool.getChildren(child)).find(c => NodeTool.getNodeName(c) === 'mtable');
927
+ const rows = Array.from(NodeTool.getChildren(mtableChild)).map(row => {
928
+ const rowChildren = Array.from(NodeTool.getChildren(row));
929
+ return rowChildren.map(cell => parse(cell)).join('');
930
+ });
931
+ return `\\begin{array}{l} ${rows.join(' \\\\ ')} \\end{array}`;
932
+ }
933
+ return parsed;
934
+ })
935
+ .join('');
936
+
937
+ // Giữ nguyên dấu ngoặc vuông lớn
938
+ return `\\left[${innerContent}\\right.`;
939
+ }
940
+
941
+ if (Brackets.isPair(firstOp, lastOp)) {
942
+ const innerContent = childrenArray
943
+ .slice(1, -1)
944
+ .map(child => parse(child))
945
+ .join('');
946
+ return `\\left${firstOp}${innerContent}\\right${lastOp}`;
947
+ }
948
+
949
+ // Giữ nguyên dấu ngoặc nhọn trong MathML
950
+ if (firstOp === '{' || lastOp === '}') {
951
+ const innerContent = childrenArray
952
+ .slice(1, lastOp === '}' ? -1 : undefined)
953
+ .map(child => parse(child))
954
+ .join('');
955
+ return `${firstOp === '{' ? '\\{' : ''}${innerContent}${lastOp === '}' ? '\\}' : ''}`;
956
+ }
957
+ }
958
+ return getRender_joinSeparator("@content")(node, childrenArray);
959
+ };
960
+ break;
961
+
835
962
  case 'msub':
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];
963
+ render = function (node, children) {
964
+ const childrenArray = Array.from(children);
965
+ if (!childrenArray || childrenArray.length < 2) return '';
966
+ const base = parse(childrenArray[0]) || '';
967
+ const sub = childrenArray[1];
843
968
  if (!sub) return base;
844
969
 
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() === '')) {
970
+ if (NodeTool.getNodeName(sub) === 'msub' &&
971
+ sub.firstChild &&
972
+ NodeTool.getNodeName(sub.firstChild) === 'mrow' &&
973
+ (!NodeTool.getNodeText(sub.firstChild) || NodeTool.getNodeText(sub.firstChild).trim() === '')) {
850
974
  const lastChild = sub.lastChild;
851
- if (!lastChild) return base;
852
- return `${base}_${parse(lastChild)}`;
975
+ return lastChild ? `${base}_${parse(lastChild)}` : base;
853
976
  }
854
-
855
- // Regular subscript
856
977
  return `${base}_{${parse(sub)}}`;
857
978
  };
858
979
  break;
980
+
859
981
  case 'msup':
860
- render = getRender_default("@1^{@2}");
982
+ render = function (node, children) {
983
+ const childrenArray = Array.from(children);
984
+ if (!childrenArray || childrenArray.length < 2) return '';
985
+ const base = parse(childrenArray[0]) || '';
986
+ const sup = parse(childrenArray[1]) || '';
987
+ return `${base}^{${sup}}`;
988
+ };
861
989
  break;
990
+
862
991
  case 'msubsup':
863
- render = getRender_default("@1_{@2}^{@3}");
992
+ render = function (node, children) {
993
+ const childrenArray = Array.from(children);
994
+ if (!childrenArray || childrenArray.length < 3) return '';
995
+ const base = parse(childrenArray[0]);
996
+ const sub = parse(childrenArray[1]);
997
+ const sup = parse(childrenArray[2]);
998
+
999
+ const lastChild = childrenArray[0].lastElementChild;
1000
+ if (lastChild && NodeTool.getNodeName(lastChild) === 'mo' &&
1001
+ NodeTool.getNodeText(lastChild).trim() === '|') {
1002
+ const content = Array.from(childrenArray[0].children)
1003
+ .slice(0, -1)
1004
+ .map(child => parse(child))
1005
+ .join('');
1006
+ return `\\left.${content}\\right|_{${sub}}^{${sup}}`;
1007
+ }
1008
+ return `${base}_{${sub}}^{${sup}}`;
1009
+ };
864
1010
  break;
1011
+
865
1012
  case 'mover':
866
- render = renderMover;
1013
+ render = function (node, children) {
1014
+ const childrenArray = Array.from(children);
1015
+ if (!childrenArray || childrenArray.length < 2) return '';
1016
+ const base = parse(childrenArray[0]) || '';
1017
+ const over = parse(childrenArray[1]) || '';
1018
+ const overText = NodeTool.getNodeText(childrenArray[1])?.trim() || '';
1019
+ const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1020
+
1021
+ if (overText === "→" && isAccent) return `\\vec{${base}}`;
1022
+ if (overText === "¯" && isAccent) return `\\overline{${base}}`;
1023
+ if (overText === "^" && isAccent) return `\\hat{${base}}`;
1024
+ return `\\overset{${over}}{${base}}`;
1025
+ };
867
1026
  break;
1027
+
868
1028
  case 'munder':
869
- render = renderMunder;
1029
+ render = function (node, children) {
1030
+ const childrenArray = Array.from(children);
1031
+ if (!childrenArray || childrenArray.length < 2) return '';
1032
+ const base = parse(childrenArray[0]) || '';
1033
+ const under = parse(childrenArray[1]) || '';
1034
+ const isUnderAccent = NodeTool.getAttr(node, "accentunder", "false") === "true";
1035
+
1036
+ if (base === "∫") return `\\int_{${under}}`;
1037
+ return `\\underset{${under}}{${base}}`;
1038
+ };
870
1039
  break;
1040
+
871
1041
  case 'munderover':
872
- render = renderMunderover;
1042
+ render = function (node, children) {
1043
+ const childrenArray = Array.from(children);
1044
+ if (!childrenArray || childrenArray.length < 3) return '';
1045
+ const base = parse(childrenArray[0]);
1046
+ const under = parse(childrenArray[1]);
1047
+ const over = parse(childrenArray[2]);
1048
+ const baseText = NodeTool.getNodeText(childrenArray[0]).trim();
1049
+
1050
+ if (baseText === '∫') return `\\int_{${under}}^{${over}}`;
1051
+ if (baseText === '∑') return `\\sum_{${under}}^{${over}}`;
1052
+ if (baseText === '∏') return `\\prod_{${under}}^{${over}}`;
1053
+ if (baseText === '|') return `\\big|_{${under}}^{${over}}`;
1054
+ return `${base}_{${under}}^{${over}}`;
1055
+ };
873
1056
  break;
1057
+
874
1058
  case 'mmultiscripts':
875
- render = renderMmultiscripts;
1059
+ render = function (node, children) {
1060
+ const childrenArray = Array.from(children);
1061
+ if (!childrenArray || childrenArray.length < 1) return '';
1062
+ const base = parse(childrenArray[0]);
1063
+ let prescripts = '';
1064
+ let postscripts = '';
1065
+ let i = 1;
1066
+
1067
+ while (i < childrenArray.length) {
1068
+ if (NodeTool.getNodeName(childrenArray[i]) === 'mprescripts') {
1069
+ i++;
1070
+ if (i + 1 < childrenArray.length) {
1071
+ prescripts = `_{${parse(childrenArray[i])}}^{${parse(childrenArray[i + 1])}}`;
1072
+ i += 2;
1073
+ }
1074
+ } else {
1075
+ if (i + 1 < childrenArray.length) {
1076
+ postscripts += `_{${parse(childrenArray[i])}}^{${parse(childrenArray[i + 1])}}`;
1077
+ i += 2;
1078
+ } else break;
1079
+ }
1080
+ }
1081
+ return `${base}${prescripts}${postscripts}`;
1082
+ };
876
1083
  break;
1084
+
1085
+ case 'mlongdiv':
1086
+ render = function (node, children) {
1087
+ const childrenArray = Array.from(children);
1088
+ if (!childrenArray || childrenArray.length < 2) return '';
1089
+ return `\\longdiv{${parse(childrenArray[0])}}{${parse(childrenArray[1])}}`;
1090
+ };
1091
+ break;
1092
+
877
1093
  case 'mroot':
878
- render = getRender_default("\\sqrt[@2]{@1}");
1094
+ render = function (node, children) {
1095
+ const childrenArray = Array.from(children);
1096
+ if (!childrenArray || childrenArray.length < 2) return '';
1097
+ const base = parse(childrenArray[0]);
1098
+ const index = parse(childrenArray[1]);
1099
+ return `\\sqrt[${index}]{${base}}`;
1100
+ };
879
1101
  break;
1102
+
880
1103
  case 'msqrt':
881
- render = getRender_joinSeparator("\\sqrt{@content}");
1104
+ render = function (node, children) {
1105
+ const childrenArray = Array.from(children);
1106
+ const content = renderChildren(childrenArray).join('');
1107
+ return `\\sqrt{${content}}`;
1108
+ };
882
1109
  break;
1110
+
883
1111
  case 'mtable':
884
- render = renderTable;
1112
+ render = function (node, children) {
1113
+ const childrenArray = Array.from(children);
1114
+
1115
+ // Kiểm tra xem mtable có phải là mtable con không
1116
+ let isNestedTable = false;
1117
+ let parent = node.parentNode;
1118
+ while (parent) {
1119
+ if (NodeTool.getNodeName(parent) === 'mtable') {
1120
+ isNestedTable = true;
1121
+ break;
1122
+ }
1123
+ parent = parent.parentNode;
1124
+ }
1125
+
1126
+ // Xử lý mỗi mtr như một hàng
1127
+ const rows = childrenArray.map(row => {
1128
+ const rowChildren = Array.from(NodeTool.getChildren(row));
1129
+ return rowChildren.map(cell => parse(cell)).join('');
1130
+ });
1131
+
1132
+ // Nếu là mtable con, chỉ trả về các hàng mà không bao bọc trong \begin{array}...\end{array}
1133
+ if (isNestedTable) {
1134
+ return rows.join(' \\\\ ');
1135
+ }
1136
+
1137
+ // Nếu mtable nằm trong mrow với dấu ngoặc vuông, sẽ được xử lý ở mrow
1138
+ let isInsideSquareBrackets = false;
1139
+ parent = node.parentNode;
1140
+ while (parent) {
1141
+ if (NodeTool.getNodeName(parent) === 'mrow') {
1142
+ const childrenOfParent = Array.from(NodeTool.getChildren(parent));
1143
+ if (childrenOfParent.length >= 2 &&
1144
+ NodeTool.getNodeName(childrenOfParent[0]) === 'mo' &&
1145
+ NodeTool.getNodeText(childrenOfParent[0]).trim() === '[') {
1146
+ isInsideSquareBrackets = true;
1147
+ break;
1148
+ }
1149
+ }
1150
+ parent = parent.parentNode;
1151
+ }
1152
+
1153
+ if (isInsideSquareBrackets) {
1154
+ return rows.join(' \\\\ ');
1155
+ }
1156
+
1157
+ // Nếu là mtable chính, bao bọc trong \begin{array}...\end{array}
1158
+ const arrayContent = rows.join(' \\\\ ');
1159
+ return `\\begin{array}{l} ${arrayContent} \\end{array}`;
1160
+ };
885
1161
  break;
1162
+
886
1163
  case 'mtr':
887
- render = getRender_joinSeparator("@content \\\\ ", ' & ');
1164
+ render = getRender_joinSeparator("@content", " & ");
888
1165
  break;
1166
+
889
1167
  case 'mtd':
890
1168
  render = getRender_joinSeparator("@content");
891
1169
  break;
1170
+
892
1171
  case 'mfrac':
893
- render = renderMfrac;
1172
+ render = function (node, children) {
1173
+ const childrenArray = Array.from(children);
1174
+ if (!childrenArray || childrenArray.length < 2) return '';
1175
+ const num = parse(childrenArray[0]);
1176
+ const den = parse(childrenArray[1]);
1177
+ const linethickness = NodeTool.getAttr(node, 'linethickness', 'medium');
1178
+ if (linethickness === '0') return `\\binom{${num}}{${den}}`;
1179
+ return `\\frac{${num}}{${den}}`;
1180
+ };
894
1181
  break;
1182
+
895
1183
  case 'mfenced':
896
- render = renderMfenced;
1184
+ render = function (node, children) {
1185
+ const childrenArray = Array.from(children);
1186
+ const open = NodeTool.getAttr(node, 'open', '(');
1187
+ const close = NodeTool.getAttr(node, 'close', ')');
1188
+ const separators = NodeTool.getAttr(node, 'separators', ',').split('');
1189
+
1190
+ // Xử lý đặc biệt cho mfenced chứa mtable
1191
+ if (open === '{' && !close) {
1192
+ // Kiểm tra xem có mtable trong cấu trúc không
1193
+ const hasMtable = childrenArray.some(child => {
1194
+ // Kiểm tra trực tiếp mtable
1195
+ if (NodeTool.getNodeName(child) === 'mtable') return true;
1196
+ // Kiểm tra mtable trong mrow
1197
+ if (NodeTool.getNodeName(child) === 'mrow') {
1198
+ return Array.from(NodeTool.getChildren(child)).some(
1199
+ grandChild => NodeTool.getNodeName(grandChild) === 'mtable'
1200
+ );
1201
+ }
1202
+ return false;
1203
+ });
1204
+
1205
+ if (hasMtable) {
1206
+ const content = childrenArray.map(child => parse(child)).join('');
1207
+ return `\\left\\{${content}\\right.`;
1208
+ }
1209
+ }
1210
+
1211
+ // Xử lý cho trường hợp [a;b)
1212
+ if (open === '[' && close === ')') {
1213
+ const parts = [];
1214
+ childrenArray.forEach((child, index) => {
1215
+ parts.push(parse(child));
1216
+ if (index < childrenArray.length - 1 && separators[index % separators.length]) {
1217
+ parts.push(separators[index % separators.length]);
1218
+ }
1219
+ });
1220
+ return `\\left[${parts.join('')}\\right)`;
1221
+ }
1222
+
1223
+ // Giữ nguyên xử lý cho các trường hợp khác
1224
+ const parts = [];
1225
+ childrenArray.forEach((child, index) => {
1226
+ parts.push(parse(child));
1227
+ if (index < childrenArray.length - 1 && separators[index % separators.length]) {
1228
+ parts.push(separators[index % separators.length]);
1229
+ }
1230
+ });
1231
+ const content = parts.join('');
1232
+
1233
+ if (open === '{' && close === '}') return `\\{${content}\\}`;
1234
+ if (open === '|' && close === '|') return `\\left|${content}\\right|`;
1235
+ if (!close) return `\\left${open}${content}\\right.`;
1236
+ if (!open) return `${content}\\right${close}`;
1237
+ return `\\left${open}${content}\\right${close}`;
1238
+ };
1239
+ break;
1240
+
1241
+ case 'menclose':
1242
+ render = function (node, children) {
1243
+ const childrenArray = Array.from(children);
1244
+ const notation = NodeTool.getAttr(node, 'notation', 'longdiv');
1245
+ const content = renderChildren(childrenArray).join('');
1246
+ switch (notation) {
1247
+ case 'box': return `\\boxed{${content}}`;
1248
+ case 'circle': return `\\enclose{circle}{${content}}`;
1249
+ case 'roundedbox': return `\\fbox{${content}}`;
1250
+ default: return content;
1251
+ }
1252
+ };
897
1253
  break;
1254
+
898
1255
  case 'mi':
899
1256
  case 'mn':
900
1257
  case 'mo':
901
1258
  case 'ms':
902
1259
  case 'mtext':
903
- // they may contains <mglyph>
904
1260
  render = getRender_joinSeparator("@content");
905
1261
  break;
1262
+
906
1263
  case 'mphantom':
907
- render = renderMphantom;
1264
+ render = function (node, children) {
1265
+ const childrenArray = Array.from(children);
1266
+ const content = renderChildren(childrenArray).join('');
1267
+ return `\\phantom{${content}}`;
1268
+ };
1269
+ break;
1270
+
1271
+ case 'mstyle':
1272
+ render = function (node, children) {
1273
+ const childrenArray = Array.from(children);
1274
+ const mathsize = NodeTool.getAttr(node, 'mathsize', 'normal');
1275
+ const content = renderChildren(childrenArray).join('');
1276
+ switch (mathsize) {
1277
+ case 'big': return `\\large{${content}}`;
1278
+ case 'small': return `\\small{${content}}`;
1279
+ default: return content;
1280
+ }
1281
+ };
908
1282
  break;
1283
+
909
1284
  default:
910
- // math, mstyle, mrow
911
1285
  render = getRender_joinSeparator("@content");
912
1286
  break;
913
1287
  }
914
1288
  return render;
915
1289
  }
916
1290
 
917
- function renderTable(node, children) {
918
- const template = "\\begin{array}{l}@content\\end{array}";
919
- // Remove extra backslash and add proper spacing
920
- const render = getRender_joinSeparator(template, " \\\\ ");
921
- return render(node, children);
922
- }
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
-
945
- function renderMfenced(node, children) {
946
- const [open, close, separatorsStr] = [
947
- NodeTool.getAttr(node, 'open', '('),
948
- NodeTool.getAttr(node, 'close', ')'),
949
- NodeTool.getAttr(node, 'separators', ',')
950
- ];
951
-
952
- const parts = renderChildren(children);
953
- const content = parts.join(separatorsStr === '|' ? ',' : separatorsStr).trim();
954
-
955
- // Handle nested absolute value cases
956
- if (open === '|' && close === '') {
957
- return `\\left|${content}`;
958
- }
959
- if (open === '' && close === '|') {
960
- return `${content}\\right|`;
961
- }
962
-
963
- // Handle system of equations with curly brace
964
- if (open === '{') {
965
- return `\\left\\{${content}${close ? '\\right\\}' : '\\right.'}`;
966
- }
967
-
968
- // Handle regular absolute value notation
969
- if (open === '|' && close === '|') {
970
- return `\\left|${content}\\right|`;
971
- }
972
-
973
- // Regular cases with proper closing
974
- return `\\left${open}${content}\\right${close || '.'}`;
975
- }
976
-
977
- function renderMfrac(node, children){
978
- const [linethickness, bevelled] = [
979
- NodeTool.getAttr(node, 'linethickness', 'medium'),
980
- NodeTool.getAttr(node, 'bevelled', 'false')
981
- ];
982
-
983
- let render = null;
984
- if(bevelled === 'true') {
985
- render = getRender_default("{}^{@1}/_{@2}");
986
- } else if(['0', '0px'].indexOf(linethickness) > -1) {
987
- const [prevNode, nextNode] = [
988
- NodeTool.getPrevNode(node),
989
- NodeTool.getNextNode(node)
990
- ];
991
- if((prevNode && NodeTool.getNodeText(prevNode).trim() === '(') &&
992
- (nextNode && NodeTool.getNodeText(nextNode).trim() === ')')
993
- ) {
994
- render = getRender_default("\\binom{@1}{@2}");
995
- } else {
996
- render = getRender_default("\\frac{@1}{@2}");
997
- }
998
- } else {
999
- render = getRender_default("\\frac{@1}{@2}");
1000
- }
1001
- return render(node, children);
1002
- }
1003
-
1004
- function renderMmultiscripts(node, children) {
1005
- if(children.length === 0) { return '' }
1006
- let sepIndex = -1;
1007
- let mprescriptsNode = null;
1008
- Array.prototype.forEach.call(children, (node) => {
1009
- if(NodeTool.getNodeName(node) === 'mprescripts'){
1010
- mprescriptsNode = node;
1011
- }
1012
- });
1013
- if(mprescriptsNode) {
1014
- sepIndex = Array.prototype.indexOf.call(children, mprescriptsNode);
1015
- }
1016
- const parts = renderChildren(children);
1017
-
1018
- const splitArray = (arr, index) => {
1019
- return [arr.slice(0, index), arr.slice(index + 1, arr.length)]
1020
- };
1021
- const renderScripts = (items) => {
1022
- if(items.length > 0) {
1023
- const subs = [];
1024
- const sups = [];
1025
- items.forEach((item, index) => {
1026
- // one render as sub script, one as super script
1027
- if((index + 1) % 2 === 0){
1028
- sups.push(item);
1029
- } else {
1030
- subs.push(item);
1031
- }
1032
- });
1033
- return [
1034
- (subs.length > 0 ? `_{${subs.join(' ')}}` : ''),
1035
- (sups.length > 0 ? `^{${sups.join(' ')}}` : '')
1036
- ].join('');
1037
- } else {
1038
- return '';
1039
- }
1040
- };
1041
- const base = parts.shift();
1042
- let prevScripts = [];
1043
- let backScripts = [];
1044
- if(sepIndex === -1){
1045
- backScripts = parts;
1046
- } else {
1047
- [backScripts, prevScripts] = splitArray(parts, sepIndex - 1);
1048
- }
1049
- return [renderScripts(prevScripts), base, renderScripts(backScripts)].join('');
1050
- }
1051
-
1052
- function renderMunder(node, children){
1053
- const nodes = flattenNodeTreeByNodeName(node, 'munder');
1054
- let result = undefined;
1055
- for(let i = 0; i < nodes.length - 1; i++) {
1056
- if(!result){ result = parse(nodes[i]); }
1057
-
1058
- const underNode = nodes[i + 1];
1059
- const underText = NodeTool.getNodeText(underNode).trim();
1060
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1061
-
1062
- // Special handling for arrow accent
1063
- if (underText === "→" && isAccent) {
1064
- return `\\underset{${result}}{\\rightarrow}`;
1065
- }
1066
-
1067
- const under = parse(underNode);
1068
- const template = getMatchValueByChar({
1069
- decimals: MathSymbol.underScript.decimals,
1070
- values: MathSymbol.underScript.templates,
1071
- judgeChar: underText,
1072
- defaultValue: "@1_{@2}"
1073
- });
1074
- result = renderTemplate(template.replace("@v", "@1"), [result, under]);
1075
- }
1076
- return result;
1077
- }
1078
-
1079
- function renderMunderover(node, children){
1080
- const nodes = flattenNodeTreeByNodeName(node, 'munderover');
1081
- let result = undefined;
1082
- for(let i = 0; i < nodes.length - 1; i++) {
1083
- if(!result){ result = parse(nodes[i]); }
1084
-
1085
- const overNode = nodes[i + 1];
1086
- const overText = NodeTool.getNodeText(overNode).trim();
1087
- const underNode = nodes[i + 2];
1088
- const underText = NodeTool.getNodeText(underNode).trim();
1089
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1090
-
1091
- // Special handling for arrow accent
1092
- if (overText === "→" && isAccent) {
1093
- return `\\overset{${result}}{\\underset{${underText}}{\\rightarrow}}`;
1094
- }
1095
-
1096
- const over = parse(overNode);
1097
- const under = parse(underNode);
1098
- const template = getMatchValueByChar({
1099
- decimals: MathSymbol.underoverScript.decimals,
1100
- values: MathSymbol.underoverScript.templates,
1101
- judgeChar: overText,
1102
- defaultValue: "@1_{@2}^{@3}"
1103
- });
1104
- result = renderTemplate(template.replace("@v", "@1"), [over, under]);
1105
- }
1106
- return result;
1107
- }
1108
-
1109
- function renderMphantom(node, children){
1110
- const nodes = flattenNodeTreeByNodeName(node, 'mphantom');
1111
- let result = undefined;
1112
- for(let i = 0; i < nodes.length - 1; i++) {
1113
- if(!result){ result = parse(nodes[i]); }
1114
-
1115
- const phantomNode = nodes[i + 1];
1116
- const phantomText = NodeTool.getNodeText(phantomNode).trim();
1117
- const isAccent = NodeTool.getAttr(node, "accent", "false") === "true";
1118
-
1119
- // Special handling for arrow accent
1120
- if (phantomText === "→" && isAccent) {
1121
- return `\\overrightarrow{${result}}`;
1122
- }
1123
-
1124
- const phantom = parse(phantomNode);
1125
- const template = getMatchValueByChar({
1126
- decimals: MathSymbol.phantomScript.decimals,
1127
- values: MathSymbol.phantomScript.templates,
1128
- judgeChar: phantomText,
1129
- defaultValue: "@1^{@2}"
1130
- });
1131
- result = renderTemplate(template.replace("@v", "@1"), [result, phantom]);
1132
- }
1133
- return result;
1134
- }
1135
-
1136
- function renderTemplate(template, args) {
1137
- return template.replace(/@(\d+)/g, (match, index) => {
1138
- const arg = args[index - 1];
1139
- return arg || match;
1140
- });
1141
- }
1142
-
1143
- function getMatchValueByChar(options) {
1144
- const { decimals, values, judgeChar, defaultValue } = options;
1145
- const match = values.find(value => value.judgeChar === judgeChar);
1146
- return match || defaultValue;
1147
- }
1148
-
1149
- function flattenNodeTreeByNodeName(node, nodeName) {
1150
- const nodes = [];
1151
- const children = NodeTool.getChildren(node);
1152
- if (children && children.length > 0) {
1153
- // Convert HTMLCollection to Array before using forEach
1154
- Array.from(children).forEach(child => {
1155
- if (NodeTool.getNodeName(child) === nodeName) {
1156
- nodes.push(child);
1157
- } else {
1158
- // Recursively search in child nodes
1159
- const childNodes = flattenNodeTreeByNodeName(child, nodeName);
1160
- nodes.push(...childNodes);
1161
- }
1162
- });
1163
- }
1164
- return nodes;
1165
- }
1166
-
1167
1291
  // Export the convert function
1168
1292
  var mathml2latex = {
1169
1293
  convert: convert