forked from Tadrisino/MathApp_frontend
MathText: last-line guard that turns stray LaTeX/markdown into plain Unicode
This commit is contained in:
77
src/lib/plainMath.ts
Normal file
77
src/lib/plainMath.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* تورِ ایمنیِ نمایش: LaTeX و نشانهگذاریِ خامی که به هر دلیل به صفحه رسیده به یونیکدِ ساده برمیگردد.
|
||||
* بکاند هم همین تبدیل را در seed، دیتابیس و پاسخِ دستیار انجام میدهد
|
||||
* (`content/plain_math.py`، `rag/services/mathtext.py`)؛ اینجا فقط آخرین لایه است تا متنِ کهنه یا
|
||||
* پاسخِ ناخواستهی مدل هرگز با `\frac` و `^{2}` دیده نشود. هر دو باید همرفتار بمانند.
|
||||
*/
|
||||
const SUP: Record<string, string> = {
|
||||
...Object.fromEntries([...'۰۱۲۳۴۵۶۷۸۹'].map((c, i) => [c, '⁰¹²³⁴⁵⁶⁷⁸⁹'[i]])),
|
||||
...Object.fromEntries([...'0123456789'].map((c, i) => [c, '⁰¹²³⁴⁵⁶⁷⁸⁹'[i]])),
|
||||
'+': '⁺', '-': '⁻', '−': '⁻', '=': '⁼', '(': '⁽', ')': '⁾',
|
||||
...Object.fromEntries([...'abcdefghijklmnoprstuvwxyzαβγδεθφχ'].map((c, i) => [c, [...'ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻᵅᵝᵞᵟᵋᶿᵠᵡ'][i]])),
|
||||
};
|
||||
const SUB: Record<string, string> = {
|
||||
...Object.fromEntries([...'۰۱۲۳۴۵۶۷۸۹'].map((c, i) => [c, '₀₁₂₃₄₅₆₇₈₉'[i]])),
|
||||
...Object.fromEntries([...'0123456789'].map((c, i) => [c, '₀₁₂₃₄₅₆₇₈₉'[i]])),
|
||||
'+': '₊', '-': '₋', '−': '₋', '=': '₌', '(': '₍', ')': '₎',
|
||||
...Object.fromEntries([...'aehijklmnoprstuvx'].map((c, i) => [c, [...'ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ'][i]])),
|
||||
};
|
||||
const SYMBOLS: Record<string, string> = {
|
||||
times: '×', cdot: '·', div: '÷', pm: '±', mp: '∓', theta: 'θ', pi: 'π', alpha: 'α', beta: 'β', gamma: 'γ', delta: 'δ',
|
||||
Delta: 'Δ', lambda: 'λ', mu: 'μ', sigma: 'σ', Sigma: 'Σ', phi: 'φ', varphi: 'φ', omega: 'ω', epsilon: 'ε', rho: 'ρ', tau: 'τ',
|
||||
infty: '∞', neq: '≠', ne: '≠', leq: '≤', le: '≤', geq: '≥', ge: '≥', to: '→', rightarrow: '→', leftarrow: '←',
|
||||
Rightarrow: '⇒', implies: '⇒', Leftrightarrow: '⇔', iff: '⇔', perp: '⊥', parallel: '∥', circ: '°', degree: '°', ldots: '…',
|
||||
dots: '…', cdots: '⋯', int: '∫', sum: 'Σ', prod: '∏', partial: '∂', in: '∈', notin: '∉', subset: '⊂', subseteq: '⊆',
|
||||
supset: '⊃', cup: '∪', cap: '∩', emptyset: '∅', varnothing: '∅', forall: '∀', exists: '∃', angle: '∠', triangle: '△',
|
||||
approx: '≈', equiv: '≡', sim: '∼', cong: '≅', mid: '|', vert: '|', neg: '¬', therefore: '∴', setminus: '−',
|
||||
quad: ' ', qquad: ' ', prime: '′',
|
||||
lim: 'lim', sin: 'sin', cos: 'cos', tan: 'tan', cot: 'cot', sec: 'sec', csc: 'csc', log: 'log', ln: 'ln', exp: 'exp',
|
||||
max: 'max', min: 'min', det: 'det', gcd: 'gcd', arcsin: 'arcsin', arccos: 'arccos', arctan: 'arctan',
|
||||
};
|
||||
const BLACKBOARD: Record<string, string> = { R: 'ℝ', Q: 'ℚ', N: 'ℕ', Z: 'ℤ', C: 'ℂ' };
|
||||
const SYMBOL_RE = new RegExp('\\\\(' + Object.keys(SYMBOLS).sort((a, b) => b.length - a.length).join('|') + ')', 'g');
|
||||
const G = '\\{([^{}]*)\\}';
|
||||
const NEEDS = /[\\^$]|[A-Za-z)]_|\*\*|__|`|^#{1,6}\s/m;
|
||||
|
||||
const atomic = (s: string) => /^[\w۰-۹.]+$/.test(s.trim());
|
||||
const wrap = (s: string) => (atomic(s) ? s.trim() : `(${s.trim()})`);
|
||||
const tryMap = (body: string, table: Record<string, string>) => {
|
||||
const b = body.trim();
|
||||
return b && [...b].every((c) => c in table) ? [...b].map((c) => table[c]).join('') : null;
|
||||
};
|
||||
const script = (body: string, table: Record<string, string>, marker: string) => {
|
||||
const mapped = tryMap(body, table);
|
||||
if (mapped) return mapped;
|
||||
const b = body.trim();
|
||||
if (marker === '^') return ` به توانِ (${b}) `;
|
||||
return /^[\w۰-۹]{1,4}$/.test(b) ? b : `_(${b})`;
|
||||
};
|
||||
|
||||
export function plainMath(text: string): string {
|
||||
if (!text || !NEEDS.test(text)) return text;
|
||||
let s = text;
|
||||
s = s.replace(/\$\$([\s\S]+?)\$\$/g, '$1').replace(/(?<![\w$])\$([^$\n]+?)\$(?![\w$])/g, '$1');
|
||||
s = s.replace(/\\[(\[]\s*|\s*\\[)\]]/g, '');
|
||||
for (let k = 0; k < 4; k++) {
|
||||
s = s.replace(new RegExp(`\\\\(?:vec|overrightarrow|overline|bar|hat|widehat)${G}`, 'g'), '$1');
|
||||
s = s.replace(new RegExp(`\\\\mathbb${G}`, 'g'), (_m, a: string) => BLACKBOARD[a] ?? a);
|
||||
s = s.replace(new RegExp(`\\\\(?:text|mathrm|mathbf|operatorname|textbf)${G}`, 'g'), '$1');
|
||||
s = s.replace(new RegExp(`\\\\[dt]?frac${G}${G}`, 'g'), (_m, a: string, b: string) => `${wrap(a)}/${wrap(b)}`);
|
||||
s = s.replace(new RegExp(`\\\\sqrt${G}`, 'g'), (_m, a: string) => `√${wrap(a)}`);
|
||||
}
|
||||
s = s.replace(new RegExp(`\\\\lim\\s*_${G}`, 'g'), 'lim($1)');
|
||||
s = s.replace(/\\(left|right)\s*/g, '').replace(/\\\|/g, '|').replace(/\\\{/g, '{').replace(/\\\}/g, '}').replace(/\\[, ]/g, ' ');
|
||||
s = s.replace(SYMBOL_RE, (_m, n: string) => SYMBOLS[n]);
|
||||
s = s.replace(new RegExp(`\\^${G}`, 'g'), (_m, a: string) => script(a, SUP, '^'));
|
||||
s = s.replace(new RegExp(`_${G}`, 'g'), (_m, a: string) => script(a, SUB, '_'));
|
||||
s = s.replace(/\^\(((?:[^()]|\([^()]*\))*)\)/g, (_m, a: string) => tryMap(a, SUP) ?? ` به توانِ (${a.trim()}) `);
|
||||
s = s.replace(/\^([+\-−]?[0-9۰-۹]+)/g, (m, a: string) => tryMap(a, SUP) ?? m);
|
||||
s = s.replace(/(?<=[A-Za-z0-9۰-۹)₀-₉])\^([A-Za-zα-ωΑ-Ω□])(?![A-Za-z0-9۰-۹])/g, (_m, a: string) => tryMap(a, SUP) ?? ` به توانِ ${a} `);
|
||||
s = s.replace(/(?<=[A-Za-z)∫Σ∏])_([0-9۰-۹]+|[A-Za-z]{1,4}[0-9۰-۹]?)/g, (_m, a: string) => [...a].map((c) => SUB[c] ?? c).join(''));
|
||||
s = s.replace(/\^([+\-−])/g, (_m, a: string) => SUP[a]);
|
||||
s = s.replace(/(?<=[A-Za-z)'])_([+\-−])/g, (_m, a: string) => SUB[a]);
|
||||
s = s.replace(/\^(√[0-9۰-۹A-Za-z]+)/g, ' به توانِ $1 ').replace(/\^([A-Za-z]{2,3})(?![A-Za-z0-9])/g, ' به توانِ $1 ');
|
||||
s = s.replace(/(?<=\S) {2,}(?=\S)/g, ' ');
|
||||
s = s.replace(/\*\*([\s\S]+?)\*\*/g, '$1').replace(/(?<![_\w])__(.+?)__(?![_\w])/g, '$1').replace(/`/g, '');
|
||||
return s.replace(/^\s{0,3}#{1,6}\s+/gm, '');
|
||||
}
|
||||
Reference in New Issue
Block a user