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 默认密钥已清空
This commit is contained in:
171
scripts/omml_mathml2latex/strategies/strategy_factory.py
Normal file
171
scripts/omml_mathml2latex/strategies/strategy_factory.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Strategy factory for creating subject-specific formatting strategies.
|
||||
|
||||
This module provides centralized strategy creation with auto-detection
|
||||
support for determining the most appropriate strategy based on content.
|
||||
"""
|
||||
|
||||
from typing import Union, List
|
||||
from ..core.interfaces import Strategy
|
||||
from .math_strategy import MathStrategy
|
||||
from .chemistry_strategy import ChemistryStrategy
|
||||
from .physics_strategy import PhysicsStrategy
|
||||
from .statistics_strategy import StatisticsStrategy
|
||||
|
||||
|
||||
class StrategyFactory:
|
||||
"""
|
||||
Factory for creating subject-specific conversion strategies.
|
||||
|
||||
This factory provides centralized strategy creation with support for:
|
||||
- Explicit strategy selection (math, chemistry, physics, statistics)
|
||||
- Auto-detection based on equation content
|
||||
- Fallback to math strategy as default
|
||||
"""
|
||||
|
||||
# Singleton instances for strategy reuse
|
||||
_math_strategy: Union[MathStrategy, None] = None
|
||||
_chemistry_strategy: Union[ChemistryStrategy, None] = None
|
||||
_physics_strategy: Union[PhysicsStrategy, None] = None
|
||||
_statistics_strategy: Union[StatisticsStrategy, None] = None
|
||||
|
||||
@classmethod
|
||||
def create_strategy(cls, subject_mode: str = "math") -> Strategy:
|
||||
"""
|
||||
Create a formatting strategy for the specified subject mode.
|
||||
|
||||
Args:
|
||||
subject_mode: The subject area for formatting
|
||||
("math", "chemistry", "physics", "statistics", or "auto")
|
||||
|
||||
Returns:
|
||||
Strategy instance for the specified subject mode
|
||||
|
||||
Examples:
|
||||
>>> factory = StrategyFactory()
|
||||
>>> strategy = factory.create_strategy("chemistry")
|
||||
>>> isinstance(strategy, ChemistryStrategy)
|
||||
True
|
||||
|
||||
>>> strategy = factory.create_strategy("math")
|
||||
>>> isinstance(strategy, MathStrategy)
|
||||
True
|
||||
|
||||
>>> strategy = factory.create_strategy("invalid")
|
||||
>>> isinstance(strategy, MathStrategy) # Falls back to math
|
||||
True
|
||||
"""
|
||||
subject_mode = subject_mode.lower()
|
||||
|
||||
if subject_mode == "chemistry":
|
||||
if cls._chemistry_strategy is None:
|
||||
cls._chemistry_strategy = ChemistryStrategy()
|
||||
return cls._chemistry_strategy
|
||||
|
||||
elif subject_mode == "physics":
|
||||
if cls._physics_strategy is None:
|
||||
cls._physics_strategy = PhysicsStrategy()
|
||||
return cls._physics_strategy
|
||||
|
||||
elif subject_mode == "statistics":
|
||||
if cls._statistics_strategy is None:
|
||||
cls._statistics_strategy = StatisticsStrategy()
|
||||
return cls._statistics_strategy
|
||||
|
||||
elif subject_mode == "auto":
|
||||
# Auto-detection handled by auto_detect_strategy()
|
||||
# For now, return math as default (will be enhanced later)
|
||||
if cls._math_strategy is None:
|
||||
cls._math_strategy = MathStrategy()
|
||||
return cls._math_strategy
|
||||
|
||||
else: # Default: "math" or any unrecognized mode
|
||||
if cls._math_strategy is None:
|
||||
cls._math_strategy = MathStrategy()
|
||||
return cls._math_strategy
|
||||
|
||||
@classmethod
|
||||
def auto_detect_strategy(cls, equation_text: str) -> Strategy:
|
||||
"""
|
||||
Auto-detect the most appropriate strategy based on equation content.
|
||||
|
||||
Args:
|
||||
equation_text: The equation text to analyze
|
||||
|
||||
Returns:
|
||||
Strategy instance best suited for the content
|
||||
|
||||
Detection priority:
|
||||
1. Chemistry (chemical elements, reaction arrows)
|
||||
2. Physics (SI units, physical constants)
|
||||
3. Statistics (statistical operators, distributions)
|
||||
4. Math (default fallback)
|
||||
|
||||
Examples:
|
||||
>>> factory = StrategyFactory()
|
||||
>>> strategy = factory.auto_detect_strategy("Fe + H2SO4 → FeSO4 + H2")
|
||||
>>> isinstance(strategy, ChemistryStrategy)
|
||||
True
|
||||
|
||||
>>> strategy = factory.auto_detect_strategy("F = ma, where a = 9.8 m/s²")
|
||||
>>> isinstance(strategy, PhysicsStrategy)
|
||||
True
|
||||
|
||||
>>> strategy = factory.auto_detect_strategy("X ~ Normal(μ, σ²)")
|
||||
>>> isinstance(strategy, StatisticsStrategy)
|
||||
True
|
||||
|
||||
>>> strategy = factory.auto_detect_strategy("sin(x) + cos(x)")
|
||||
>>> isinstance(strategy, MathStrategy)
|
||||
True
|
||||
"""
|
||||
# Create strategy instances if not already created
|
||||
if cls._chemistry_strategy is None:
|
||||
cls._chemistry_strategy = ChemistryStrategy()
|
||||
if cls._physics_strategy is None:
|
||||
cls._physics_strategy = PhysicsStrategy()
|
||||
if cls._statistics_strategy is None:
|
||||
cls._statistics_strategy = StatisticsStrategy()
|
||||
if cls._math_strategy is None:
|
||||
cls._math_strategy = MathStrategy()
|
||||
|
||||
# Priority order: Chemistry > Physics > Statistics > Math
|
||||
# (Chemistry has most specific indicators)
|
||||
|
||||
if cls._chemistry_strategy.detect_context(equation_text):
|
||||
return cls._chemistry_strategy
|
||||
|
||||
if cls._physics_strategy.detect_context(equation_text):
|
||||
return cls._physics_strategy
|
||||
|
||||
if cls._statistics_strategy.detect_context(equation_text):
|
||||
return cls._statistics_strategy
|
||||
|
||||
# Default: math strategy
|
||||
return cls._math_strategy
|
||||
|
||||
@classmethod
|
||||
def reset_cache(cls):
|
||||
"""
|
||||
Reset the cached strategy instances.
|
||||
|
||||
This is useful for testing or when strategy implementations change.
|
||||
"""
|
||||
cls._math_strategy = None
|
||||
cls._chemistry_strategy = None
|
||||
cls._physics_strategy = None
|
||||
cls._statistics_strategy = None
|
||||
|
||||
@classmethod
|
||||
def get_available_strategies(cls) -> List[str]:
|
||||
"""
|
||||
Get list of available strategy names.
|
||||
|
||||
Returns:
|
||||
List of strategy names that can be used with create_strategy()
|
||||
"""
|
||||
return ["math", "chemistry", "physics", "statistics", "auto"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""String representation for debugging."""
|
||||
return "StrategyFactory(math, chemistry, physics, statistics, auto)"
|
||||
Reference in New Issue
Block a user