Files
DoLuEdu-WordFileParse/scripts/omml_mathml2latex/visitors/mglyph_visitor.py
Quentin 3271fee5cc init: WordFileParse docx→HTML 解析服务(含 Word EQ 域公式修复)
服务:Spring Boot 3.2 + docx4j 8.3.9,POST /api/v3/server_office_file/file/parse
      接受 .docx,返回 HTML(公式 → LaTeX,图片 → RustFS 外链)

公式支持三类来源:
- OMML(Word 2007+ 公式)      → Python omml_mathml2latex
- MathType OLE 对象            → Ruby mathtype2mathml → MathML → LaTeX
- Word EQ 域(老教辅主力)      → EqToLatexUtil(本次新增)

本次改动:
1. 新增 java-svc/.../core/EqToLatexUtil.java:
   移植自 jby backend-services office-core,EQ 语法 → LaTeX
   (\f→\dfrac、\r→\sqrt、\i→\int/\sum/\prod、\s→^{}/_{}、\b→\left\right、
     \a→array、\x→\boxed/\overline、\o\al 堆叠、圈码、化学符号、极限、连等式)
   品牌标记 JBYU* → WPU*,与采集端一致
2. CtoMathFinder:接上 EQ 分支(原为 not-ported 桩),域代码经
   EqToLatexUtil.convert 转 LaTeX 写入 eqLatexMap(WPEQNUM 占位符)
3. WordParseUtil.dealPlaceholder:EQ 的 LaTeX 同样加 \( \) 包裹
   (原先因 WPEQNUM 例外不加包裹,前端/MathJax 不渲染)

修前症状:教辅 Word 里分式/方程组/上下标整段丢失(如 {−1/3, 1} 变成 {-,1})
修后实测:46 份教辅 2908 处 EQ 域全部还原,公式总数 2954 处、零未包裹残留

安全:application.yml(含 RustFS 密钥)不入库,提供 application.yml.example;
      OssService 的 @Value 默认密钥已清空
2026-09-29 09:43:13 +08:00

106 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

r"""
Visitor for <mglyph> (custom glyph) MathML elements.
The <mglyph> element is used to display non-standard symbols or images
within mathematical expressions. It references external images via the
'src' attribute or font glyphs via 'fontfamily' and 'index' attributes.
In LaTeX conversion, mglyph is converted to text representations or
fallback Unicode characters when available. For image-based glyphs,
alt text is used if provided.
"""
from lxml import etree as ET
from ..core.interfaces import Visitor, Strategy
from ..utils import local_name as _local, text_content as _text_content
class MglyphVisitor(Visitor):
r"""
Handles <mglyph> (custom glyph) MathML elements.
Converts custom glyphs to LaTeX text or fallback Unicode. Since LaTeX
doesn't support arbitrary image references, we use alt text or text
content as fallback.
Examples:
Glyph with alt text:
<mglyph src="myGlyph.png" alt="custom symbol"/>
→ \text{custom symbol}
Glyph with font reference:
<mglyph fontfamily="MyFont" index="42" alt="α"/>
→ \text{α}
Glyph without alt (uses src filename):
<mglyph src="alpha.png"/>
→ \text{alpha}
Glyph with width/height (ignored in LaTeX):
<mglyph src="beta.png" width="2em" height="2em" alt="β"/>
→ \text{β}
Note:
LaTeX doesn't support external image references in math mode,
so we convert to text representations using alt attribute or
derive from src filename.
"""
def __init__(self, strategy: Strategy):
"""Initialize the Mglyph visitor."""
super().__init__(strategy)
def visit(self, node: ET.Element) -> str:
r"""
Convert <mglyph> element to LaTeX text.
Args:
node: The <mglyph> element to convert
Returns:
LaTeX string with \text{...} fallback
Raises:
ValueError: If node is not a <mglyph> element
"""
if not self.can_handle(node):
raise ValueError(f"MglyphVisitor cannot handle element '{node.tag}'")
# Try to get alt text (primary fallback)
alt_text = node.get("alt", "")
if alt_text:
# Use alt text if provided
return f"\\text{{{alt_text}}}"
# Fallback: try to derive from src attribute
src = node.get("src", "")
if src:
# Extract filename without extension as fallback
import os
filename = os.path.splitext(os.path.basename(src))[0]
if filename:
return f"\\text{{{filename}}}"
# Last resort: fontfamily reference or empty
fontfamily = node.get("fontfamily", "")
if fontfamily:
# Can't do much with font references in LaTeX
return f"\\text{{{fontfamily}}}"
# Completely empty - render as empty
return ""
def can_handle(self, node: ET.Element) -> bool:
"""Check if this visitor can handle the given node."""
return _local(node.tag) == 'mglyph'
@property
def element_name(self) -> str:
"""Get the element name this visitor handles."""
return 'mglyph'
def __repr__(self) -> str:
"""String representation for debugging."""
return f"MglyphVisitor(strategy={self.strategy.name})"