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