diff --git a/lib/services/mathml_converter.dart b/lib/services/mathml_converter.dart
index 86a6a247..d40f61b6 100644
--- a/lib/services/mathml_converter.dart
+++ b/lib/services/mathml_converter.dart
@@ -1,14 +1,95 @@
import 'package:xml/xml.dart';
-import './logs_helper.dart';
+import 'package:wispar/services/logs_helper.dart';
-// Converts MathML to latex equations so that they can be rendered with Latext
class MathmlToLatexConverter {
final logger = LogsService().logger;
+
+ final _functionNames = {
+ 'sin': r'\sin ',
+ 'cos': r'\cos ',
+ 'tan': r'\tan ',
+ 'log': r'\log ',
+ 'ln': r'\ln ',
+ 'lim': r'\lim ',
+ 'exp': r'\exp ',
+ 'min': r'\min ',
+ 'max': r'\max ',
+ 'sinh': r'\sinh ',
+ 'cosh': r'\cosh ',
+ 'tanh': r'\tanh ',
+ };
+
+ final _specialChars = {
+ 'α': r'\alpha ',
+ 'β': r'\beta ',
+ 'γ': r'\gamma ',
+ 'δ': r'\delta ',
+ 'Δ': r'\Delta ',
+ 'λ': r'\lambda ',
+ 'μ': r'\mu ',
+ 'π': r'\pi ',
+ 'σ': r'\sigma ',
+ 'Ω': r'\Omega ',
+ 'τ': r'\tau ',
+ 'ν': r'\nu ',
+ 'θ': r'\theta ',
+ 'φ': r'\phi ',
+ 'ψ': r'\psi ',
+ 'η': r'\eta ',
+ '±': r'\pm ',
+ '→': r'\to ',
+ '∞': r'\infty ',
+ '≈': r'\approx ',
+ '≠': r'\neq ',
+ '×': r'\times ',
+ '·': r'\cdot ',
+ '°': r'^\circ ',
+ '−': '-',
+ '—': '-',
+ '‾': r'\bar',
+ '∘': r'\circ ',
+ '⁺': r'^+',
+ '⁻': '^-',
+ '⁰': r'^0',
+ '⊂': r'\subset ',
+ };
+
String convert(String raw) {
try {
- final wrapped = '$raw';
+ final cleaned = raw
+ .replaceAllMapped(RegExp(r'\$\$(.*?)\$\$', dotAll: true),
+ (m) => '\$${m.group(1)}\$')
+ .replaceAll('\u00A0', ' ')
+ .replaceAll(' ', ' ')
+ .replaceAll('⁢', '')
+ .replaceAll('⁢', '')
+ .replaceAll('⋅', '·')
+ .replaceAll('\u2062', '')
+ .replaceAll('\u2212', '-')
+ .replaceAll('\u203e', r'\bar')
+ .replaceAll('\u2218', r'\circ ');
+
+ final wrapped =
+ '$cleaned';
final document = XmlDocument.parse(wrapped);
- return _convertChildren(document.rootElement.children);
+
+ String result = _convertChildren(document.rootElement.children).trim();
+
+ return result
+ .replaceAll(RegExp(r'\$\.\$'), '.')
+ .replaceAll(RegExp(r'\$,\$'), ',')
+ .replaceAllMapped(RegExp(r'(\d)\s*([.,])\s*(\d)'),
+ (match) => match.group(1)! + match.group(2)! + match.group(3)!)
+ .replaceAllMapped(
+ RegExp(r'([a-zA-Z0-9]+)\s*\$(\{\})?(_{.*?}|\^{.*?})\$'),
+ (match) => '\$' + match.group(1)! + match.group(3)! + '\$')
+ .replaceAllMapped(RegExp(r'\$(\{\})?(_{.*?}|\^{.*?})\$'),
+ (match) => '\${}' + match.group(2)! + '\$')
+ .replaceAll('\$\$', '')
+ .replaceAll(RegExp(r'\$\s+\$'), ' ')
+ .replaceAll(RegExp(r'\$+\$'), '\$')
+ .replaceAll(RegExp(r' +'), ' ')
+ .trim();
} catch (e, stackTrace) {
logger.warning('Unable to convert MathML to Latex.', e, stackTrace);
return raw;
@@ -17,155 +98,153 @@ class MathmlToLatexConverter {
String _convertChildren(List nodes) {
final buffer = StringBuffer();
- final formulaBuffer = StringBuffer();
- bool insideFormula = false;
-
- for (int i = 0; i < nodes.length; i++) {
- final node = nodes[i];
-
+ for (var node in nodes) {
if (node is XmlText) {
- final text = node.value;
- final trimmed = text.trimRight();
-
- final isChemicalSymbol =
- RegExp(r'^[A-Z][a-z]?$').hasMatch(trimmed.trim());
-
- if (isChemicalSymbol) {
- // Start formula buffering if not already
- if (!insideFormula) {
- insideFormula = true;
- formulaBuffer.clear();
- }
- formulaBuffer.write(trimmed.trim());
- } else {
- // Flush formula if any
- if (insideFormula && formulaBuffer.isNotEmpty) {
- final formula = '\$${formulaBuffer.toString()}\$';
- if (buffer.isNotEmpty && !buffer.toString().endsWith(' ')) {
- buffer.write(' ');
- }
- buffer.write(formula);
- if (!formula.endsWith('.')) {
- buffer.write(' ');
- }
- formulaBuffer.clear();
- insideFormula = false;
- }
- buffer.write(trimmed); // regular text
- }
- } else if (node is XmlElement && node.name.local == 'math') {
- final mathContent = _convertChildren(node.children);
- if (!insideFormula) {
- insideFormula = true;
- formulaBuffer.clear();
- }
- formulaBuffer.write(mathContent);
-
- // Look ahead: is next another