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:
2026-09-29 09:43:13 +08:00
commit 3271fee5cc
360 changed files with 86082 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.jby.common.exceptions.BusinessException
* org.apache.commons.pool2.BasePooledObjectFactory
* org.apache.commons.pool2.PooledObject
* org.apache.commons.pool2.PooledObjectFactory
* org.apache.commons.pool2.impl.DefaultPooledObject
* org.apache.commons.pool2.impl.GenericObjectPool
* org.apache.commons.pool2.impl.GenericObjectPoolConfig
* org.slf4j.Logger
* org.slf4j.LoggerFactory
* org.springframework.core.io.ClassPathResource
*/
package com.jby.officecore.pool;
import com.jby.common.exceptions.BusinessException;
import java.util.Objects;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
public enum OMml2MmlTransformerPool {
INSTANCE;
private static final Logger log;
private GenericObjectPool<Transformer> objectPool;
private OMml2MmlTransformerPool() {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(30);
poolConfig.setMinIdle(4);
poolConfig.setMaxIdle(8);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWaitMillis(3000L);
poolConfig.setTestOnCreate(true);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(true);
poolConfig.setTestWhileIdle(true);
PooledTransformerFactory objectFactory = new PooledTransformerFactory();
this.objectPool = new GenericObjectPool((PooledObjectFactory)objectFactory, poolConfig);
}
public Transformer borrowObject() {
log.info("\u8f6c\u6362\u5668\u6c60TransformerPool, \u7a7a\u95f2\u5bf9\u8c61\u6570\u91cf:{}, \u6d3b\u8dc3\u5bf9\u8c61\u6570\u91cf:{}, \u963b\u585e\u7b49\u5f85\u6570\u91cf:{},", new Object[]{this.objectPool.getNumIdle(), this.objectPool.getNumActive(), this.objectPool.getNumWaiters()});
try {
return (Transformer)this.objectPool.borrowObject();
}
catch (Exception e) {
log.error("\u4ece\u5bf9\u8c61\u6c60\u4e2d\u501f\u51fa\u5bf9\u8c61\u5f02\u5e38", (Throwable)e);
return null;
}
}
public void returnObject(Transformer transformer) {
log.info("\u8f6c\u6362\u5668\u6c60TransformerPool, \u7a7a\u95f2\u5bf9\u8c61\u6570\u91cf:{}, \u6d3b\u8dc3\u5bf9\u8c61\u6570\u91cf:{}, \u963b\u585e\u7b49\u5f85\u6570\u91cf:{},", new Object[]{this.objectPool.getNumIdle(), this.objectPool.getNumActive(), this.objectPool.getNumWaiters()});
if (Objects.nonNull(transformer)) {
this.objectPool.returnObject((Object)transformer);
}
}
static {
log = LoggerFactory.getLogger(OMml2MmlTransformerPool.class);
}
public static class PooledTransformerFactory
extends BasePooledObjectFactory<Transformer> {
private static final Logger log = LoggerFactory.getLogger(PooledTransformerFactory.class);
private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
public Transformer create() throws Exception {
return PooledTransformerFactory.buildTransformer();
}
public PooledObject<Transformer> wrap(Transformer transformer) {
return new DefaultPooledObject((Object)transformer);
}
public void destroyObject(PooledObject<Transformer> p) throws Exception {
super.destroyObject(p);
}
public boolean validateObject(PooledObject<Transformer> p) {
return Objects.nonNull(p.getObject());
}
private static Transformer buildTransformer() {
try {
ClassPathResource resource = new ClassPathResource("xsl/OMML2MML.XSL");
StreamSource xslSource = new StreamSource(resource.getInputStream());
return TRANSFORMER_FACTORY.newTransformer(xslSource);
}
catch (Exception e) {
log.error("\u751f\u6210\u8f6c\u6362\u5668\u5f02\u5e38", (Throwable)e);
throw new BusinessException("\u751f\u6210\u8f6c\u6362\u5668\u5f02\u5e38");
}
}
}
}