Files
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

172 lines
6.0 KiB
Python

"""
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)"