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:
163
scripts/omml_mathml2latex/core/context.py
Normal file
163
scripts/omml_mathml2latex/core/context.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Conversion context data structure.
|
||||
|
||||
This module defines the ConversionContext dataclass that holds all state
|
||||
and intermediate results throughout the conversion pipeline.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class ConversionContext:
|
||||
"""
|
||||
Shared state for the conversion pipeline.
|
||||
|
||||
This context object flows through all pipeline stages, accumulating
|
||||
intermediate results and maintaining conversion state.
|
||||
|
||||
Attributes:
|
||||
input_type: Type of input ('docx', 'omml', 'mathml', 'stdin')
|
||||
subject_mode: Subject area hint ('math', 'chemistry', 'physics', 'statistics', 'auto')
|
||||
wrap_identifiers: Whether to wrap identifiers in \\mathrm{} (default: True)
|
||||
prefer_xlongequal: Use \\xlongequal for overset equals (default: True)
|
||||
|
||||
# Intermediate results
|
||||
omml: OMML XML string (if applicable)
|
||||
mathml: MathML XML string
|
||||
latex: Generated LaTeX string
|
||||
|
||||
# Validation state
|
||||
validation_errors: List of validation error messages
|
||||
validation_warnings: List of validation warning messages
|
||||
is_valid: Whether MathML passed validation
|
||||
|
||||
# Color optimization state
|
||||
uniform_equation_color: Hex color if entire equation uses same color, else empty
|
||||
suppress_leaf_colors: Flag to suppress individual element colors when uniform color detected
|
||||
|
||||
# Metadata
|
||||
equation_count: Number of equations processed
|
||||
current_equation_index: Index of current equation being processed
|
||||
context_hints: Additional context information
|
||||
|
||||
# Performance metrics
|
||||
stage_timings: Dict mapping stage name to execution time (seconds)
|
||||
total_time: Total processing time (seconds)
|
||||
"""
|
||||
|
||||
# Configuration
|
||||
input_type: str
|
||||
subject_mode: str = "math"
|
||||
wrap_identifiers: bool = True
|
||||
prefer_xlongequal: bool = True
|
||||
|
||||
# Intermediate results
|
||||
omml: Optional[str] = None
|
||||
mathml: Optional[str] = None
|
||||
latex: Optional[str] = None
|
||||
|
||||
# Validation results
|
||||
validation_errors: List[str] = field(default_factory=list)
|
||||
validation_warnings: List[str] = field(default_factory=list)
|
||||
is_valid: bool = True
|
||||
|
||||
# Color optimization state
|
||||
uniform_equation_color: str = ""
|
||||
suppress_leaf_colors: bool = False
|
||||
|
||||
# Script context tracking (for fraction sizing)
|
||||
script_context_stack: List[str] = field(default_factory=list)
|
||||
display_style_context: bool = False
|
||||
|
||||
# Set theory mode (for unclosed set notation)
|
||||
set_theory_mode: bool = False
|
||||
|
||||
# Chemistry equation detection (cached)
|
||||
is_chemistry_equation: Optional[bool] = None
|
||||
|
||||
# Metadata
|
||||
equation_count: int = 0
|
||||
current_equation_index: int = 0
|
||||
context_hints: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Performance metrics
|
||||
stage_timings: Dict[str, float] = field(default_factory=dict)
|
||||
total_time: float = 0.0
|
||||
|
||||
def add_error(self, error: str) -> None:
|
||||
"""Add a validation error."""
|
||||
self.validation_errors.append(error)
|
||||
self.is_valid = False
|
||||
|
||||
def add_warning(self, warning: str) -> None:
|
||||
"""Add a validation warning."""
|
||||
self.validation_warnings.append(warning)
|
||||
|
||||
def record_stage_time(self, stage_name: str, time_seconds: float) -> None:
|
||||
"""Record execution time for a stage."""
|
||||
self.stage_timings[stage_name] = time_seconds
|
||||
|
||||
def has_errors(self) -> bool:
|
||||
"""Check if any errors were recorded."""
|
||||
return len(self.validation_errors) > 0
|
||||
|
||||
def has_warnings(self) -> bool:
|
||||
"""Check if any warnings were recorded."""
|
||||
return len(self.validation_warnings) > 0
|
||||
|
||||
def clear_validation_state(self) -> None:
|
||||
"""Clear validation errors and warnings."""
|
||||
self.validation_errors.clear()
|
||||
self.validation_warnings.clear()
|
||||
self.is_valid = True
|
||||
|
||||
def push_script_context(self, element_type: str) -> None:
|
||||
"""
|
||||
Push a script context (msup/msub/msubsup) onto the stack.
|
||||
|
||||
Args:
|
||||
element_type: The element type entering script context
|
||||
"""
|
||||
self.script_context_stack.append(element_type)
|
||||
|
||||
def pop_script_context(self) -> Optional[str]:
|
||||
"""
|
||||
Pop a script context from the stack.
|
||||
|
||||
Returns:
|
||||
The popped element type, or None if stack was empty
|
||||
"""
|
||||
return self.script_context_stack.pop() if self.script_context_stack else None
|
||||
|
||||
def in_script_context(self) -> bool:
|
||||
"""Check if currently inside a script context (superscript/subscript)."""
|
||||
return len(self.script_context_stack) > 0
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""String representation for debugging."""
|
||||
status = "valid" if self.is_valid else "invalid"
|
||||
return (
|
||||
f"ConversionContext("
|
||||
f"input_type={self.input_type}, "
|
||||
f"subject_mode={self.subject_mode}, "
|
||||
f"status={status}, "
|
||||
f"equations={self.equation_count})"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Detailed representation for debugging."""
|
||||
return (
|
||||
f"ConversionContext(\n"
|
||||
f" input_type={self.input_type!r},\n"
|
||||
f" subject_mode={self.subject_mode!r},\n"
|
||||
f" wrap_identifiers={self.wrap_identifiers},\n"
|
||||
f" prefer_xlongequal={self.prefer_xlongequal},\n"
|
||||
f" is_valid={self.is_valid},\n"
|
||||
f" equation_count={self.equation_count},\n"
|
||||
f" errors={len(self.validation_errors)},\n"
|
||||
f" warnings={len(self.validation_warnings)},\n"
|
||||
f" total_time={self.total_time:.4f}s\n"
|
||||
f")"
|
||||
)
|
||||
Reference in New Issue
Block a user