403Webshell
Server IP : 3.147.158.171  /  Your IP : 216.73.216.216
Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5
System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64
User : ec2-user ( 1000)
PHP Version : 8.4.21
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /tsai/www/html/api.turmansolutions.ai/app/utilities/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /tsai/www/html/api.turmansolutions.ai/app/utilities/math_tex.py
"""Inline LaTeX-math normalization for article HTML.

AI-generated article bodies occasionally wrap short formulas in TeX math
delimiters, e.g. ``$$\\nabla_\\text{rad} > \\nabla_\\text{ad}$$``. The shared
``MarkdownIt`` parser (commonmark) has no math plugin and the WordPress themes
load no MathJax/KaTeX, so the raw TeX survives rendering and leaks onto the
published page as literal ``$$...$$`` text.

`convert_math` runs on the *final* HTML (after markdown rendering, like
`normalize_citations`) and rewrites the small subset of TeX these articles use
into plain Unicode + ``<sub>``/``<sup>`` markup that renders everywhere with no
front-end dependency:

    $$\\nabla_\\text{rad} > \\nabla_\\text{ad}$$
        -> <span class="tsai-math">∇<sub>rad</sub> &gt; ∇<sub>ad</sub></span>

It is a best-effort transform: constructs it does not recognize degrade to
cleaned-up text (delimiters and backslashes stripped) rather than raw TeX. It
is idempotent — once the ``$``-delimiters are gone there is nothing left to
match — so it is safe to re-run on its own output.
"""

from __future__ import annotations

import re

# --- TeX token tables ---------------------------------------------------------

# Font/roman wrappers: keep the braced group, drop the command so the grouping
# survives for subscript/superscript handling (``\text{rad}`` -> ``{rad}``).
_FONT_CMDS = re.compile(r'\\(?:text|mathrm|mathbf|mathit|mathsf|mathcal|operatorname)\s*\{')

# Whitespace / kerning commands that carry no glyph.
_SPACING = re.compile(r'\\(?:,|;|:|!|quad|qquad|thinspace|;|\s)')

# Symbol and Greek-letter names -> Unicode. Longest names must be tried first so
# ``\nabla`` isn't shadowed by a shorter prefix.
_SYMBOLS = {
    # operators / relations
    'times': '×', 'cdot': '·', 'div': '÷', 'pm': '±', 'mp': '∓',
    'leq': '≤', 'le': '≤', 'geq': '≥', 'ge': '≥', 'neq': '≠', 'ne': '≠',
    'approx': '≈', 'equiv': '≡', 'sim': '∼', 'simeq': '≃', 'propto': '∝',
    'll': '≪', 'gg': '≫', 'll ': '≪', 'gg ': '≫',
    'rightarrow': '→', 'to': '→', 'leftarrow': '←', 'leftrightarrow': '↔',
    'Rightarrow': '⇒', 'Leftarrow': '⇐', 'mapsto': '↦',
    'infty': '∞', 'partial': '∂', 'nabla': '∇', 'sqrt': '√',
    'sum': '∑', 'prod': '∏', 'int': '∫', 'oint': '∮',
    'odot': '⊙', 'oplus': '⊕', 'otimes': '⊗', 'star': '⋆', 'ast': '∗',
    'prime': '′', 'circ': '∘', 'deg': '°', 'angle': '∠',
    'in': '∈', 'notin': '∉', 'subset': '⊂', 'supset': '⊃',
    'cup': '∪', 'cap': '∩', 'forall': '∀', 'exists': '∃',
    'langle': '⟨', 'rangle': '⟩', 'ldots': '…', 'cdots': '⋯',
    # lowercase Greek
    'alpha': 'α', 'beta': 'β', 'gamma': 'γ', 'delta': 'δ', 'epsilon': 'ε',
    'varepsilon': 'ε', 'zeta': 'ζ', 'eta': 'η', 'theta': 'θ', 'vartheta': 'ϑ',
    'iota': 'ι', 'kappa': 'κ', 'lambda': 'λ', 'mu': 'µ', 'nu': 'ν', 'xi': 'ξ',
    'pi': 'π', 'varpi': 'ϖ', 'rho': 'ρ', 'varrho': 'ϱ', 'sigma': 'σ',
    'varsigma': 'ς', 'tau': 'τ', 'upsilon': 'υ', 'phi': 'φ', 'varphi': 'ϕ',
    'chi': 'χ', 'psi': 'ψ', 'omega': 'ω',
    # uppercase Greek
    'Gamma': 'Γ', 'Delta': 'Δ', 'Theta': 'Θ', 'Lambda': 'Λ', 'Xi': 'Ξ',
    'Pi': 'Π', 'Sigma': 'Σ', 'Upsilon': 'Υ', 'Phi': 'Φ', 'Psi': 'Ψ',
    'Omega': 'Ω',
}
_SYMBOL_RE = re.compile(
    r'\\(' + '|'.join(sorted(_SYMBOLS, key=len, reverse=True)) + r')(?![A-Za-z])'
)

# Display ($$...$$) and inline ($...$) math spans. Non-greedy, single line.
_DISPLAY = re.compile(r'\$\$(.+?)\$\$', re.S)
_INLINE = re.compile(r'\$(?!\$)([^$\n]+?)\$')

# Subscript / superscript: braced group first, then a single character.
_SUB_BRACE = re.compile(r'_\{([^{}]*)\}')
_SUP_BRACE = re.compile(r'\^\{([^{}]*)\}')
_SUB_CHAR = re.compile(r'_([^\s_^{}<&])')
_SUP_CHAR = re.compile(r'\^([^\s_^{}<&])')

# ``\frac{a}{b}`` (non-nested) -> ``a/b``.
_FRAC = re.compile(r'\\frac\s*\{([^{}]*)\}\s*\{([^{}]*)\}')

# Leftover unknown command: strip the backslash, keep the name as text.
_LEFTOVER_CMD = re.compile(r'\\([A-Za-z]+)')


def _convert_tex(tex: str) -> str:
    """Translate a single TeX math fragment (delimiters already removed)."""
    s = tex
    s = _SPACING.sub(' ', s)
    s = _FRAC.sub(r'\1/\2', s)
    s = _FONT_CMDS.sub('{', s)          # \text{rad} -> {rad}
    s = _SYMBOL_RE.sub(lambda m: _SYMBOLS[m.group(1)], s)
    s = _SUB_BRACE.sub(r'<sub>\1</sub>', s)
    s = _SUP_BRACE.sub(r'<sup>\1</sup>', s)
    s = _SUB_CHAR.sub(r'<sub>\1</sub>', s)
    s = _SUP_CHAR.sub(r'<sup>\1</sup>', s)
    s = _LEFTOVER_CMD.sub(r'\1', s)     # unknown \cmd -> cmd (readable fallback)
    s = s.replace('{', '').replace('}', '')
    s = re.sub(r'[ \t]{2,}', ' ', s).strip()
    return s


def convert_math(html: str) -> str:
    """Rewrite TeX math spans in ``html`` to Unicode + ``<sub>``/``<sup>`` markup.

    Handles display ``$$...$$`` and inline ``$...$`` delimiters. Inline spans are
    only converted when they actually look like math (contain a backslash, brace,
    or sub/superscript) so ordinary prose with currency amounts is left alone.
    Idempotent and safe to run on HTML that contains no math.
    """
    if not html or '$' not in html:
        return html

    def _display_repl(m: "re.Match[str]") -> str:
        return f'<span class="tsai-math">{_convert_tex(m.group(1))}</span>'

    def _inline_repl(m: "re.Match[str]") -> str:
        body = m.group(1)
        if not re.search(r'[\\{}_^]', body):
            return m.group(0)  # not math-like (e.g. "$5 to $10") — leave intact
        return f'<span class="tsai-math">{_convert_tex(body)}</span>'

    html = _DISPLAY.sub(_display_repl, html)
    html = _INLINE.sub(_inline_repl, html)
    return html

Youez - 2016 - github.com/yon3zu
LinuXploit