fix(omml2latex): fragmented-function 前缀匹配,修复 sinx 误输出

mrow_visitor 的 _check_fragmented_function_at_position 把所有连续单字母
拼成整串后只查整串是否在 MATH_FUNCTIONS,导致 <mi>s</mi><mi>i</mi><mi>n</mi>
<mi>x</mi> 输出 'sinx' 而非 '\sin x'。

改为前缀匹配(对齐 legacy_adapter._check_fragmented_function_at_level),
从最长到最短找已知函数名,只消耗函数名对应字母,其余留给调用方。

验证:sinx/cosx/tanx → \sin x/\cos x/\tan x;纯 sin/log、log_2(msub)、
BC 拼接、abc 非函数等场景零回归;test_mixed.docx 130 公式输出不变。
This commit is contained in:
2026-10-15 01:27:13 +08:00
parent d4c2fd1fca
commit 93d070930a

View File

@@ -546,14 +546,28 @@ class MrowVisitor(Visitor):
if len(letters) < 2: if len(letters) < 2:
return ("", 0, None) return ("", 0, None)
# Check if the sequence forms a known function # Check if the sequence forms a known function.
# Use prefix matching so that "sinx" resolves to "sin" (leaving the
# trailing "x" for the caller to convert), rather than requiring the
# whole run to be a function name. Mirrors the logic in
# legacy_adapter._check_fragmented_function_at_level.
from ..data import MATH_FUNCTIONS from ..data import MATH_FUNCTIONS
potential_function = ''.join(letters) potential_function = ""
matched_length = 0
for length in range(min(len(letters), 6), 1, -1):
candidate = ''.join(letters[:length])
if candidate in MATH_FUNCTIONS:
potential_function = candidate
matched_length = length
break
if not potential_function:
if potential_function not in MATH_FUNCTIONS:
return ("", 0, None) return ("", 0, None)
# Only the matched letters belong to the function; any trailing letters
# (e.g. the "x" in "sinx") remain for the caller to convert.
consumed_count = matched_length
# Build the function LaTeX # Build the function LaTeX
function_latex = f"\\{potential_function}" function_latex = f"\\{potential_function}"