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:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 构建产物
|
||||||
|
java-svc/target/
|
||||||
|
java-svc-jdk8/target/
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
!scripts/**/*.jar
|
||||||
|
|
||||||
|
# 含密钥的本地配置(仓库里只放 application.yml.example)
|
||||||
|
java-svc/src/main/resources/application.yml
|
||||||
|
|
||||||
|
# 备份/临时
|
||||||
|
*.bak-*
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
.DS_Store
|
||||||
|
__MACOSX/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
413
java-src/config/OfficeCoreConfig.java
Normal file
413
java-src/config/OfficeCoreConfig.java
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jby.common.util.SpringContextHolder
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.springframework.boot.context.properties.ConfigurationProperties
|
||||||
|
* org.springframework.cloud.context.config.annotation.RefreshScope
|
||||||
|
* org.springframework.stereotype.Component
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.config;
|
||||||
|
|
||||||
|
import com.jby.common.util.SpringContextHolder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.cloud.context.config.annotation.RefreshScope;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RefreshScope
|
||||||
|
@ConfigurationProperties(prefix="office.core.config")
|
||||||
|
public class OfficeCoreConfig {
|
||||||
|
private String tmpPath = "/opt/files/tmp";
|
||||||
|
private String templatePath;
|
||||||
|
private Boolean deleteTmpFile = true;
|
||||||
|
private String foreLatexToMathMlApi = "https://test.xinjiaoyu.com/api/v3/server_node_sheet_new/latex/to-mathml";
|
||||||
|
private String latexToMathMlApi = "http://172.26.197.220:8050/latex_to_mathml";
|
||||||
|
private String mathMlToLatexApi = "http://172.26.197.220:8050/mathml_to_latex";
|
||||||
|
private Boolean mathMlToLatexApiSwitch;
|
||||||
|
private String mf2SvgApi = "http://test.format.xinjiaoyu.com/server_format/graphic/mf2svg";
|
||||||
|
private String downloadPdfApi = "https://test.xinjiaoyu.com/api/v3/server_node_sheet_new/office/download";
|
||||||
|
private String errorBookTemplate = "https://test.xinjiaoyu.com/main/print.html?%s#/downloadNotebook";
|
||||||
|
private Integer nodeConnectTimeout = 5000;
|
||||||
|
private Integer nodeSocketTimeout = 30000;
|
||||||
|
private Long contentLimit = 100000L;
|
||||||
|
private Double limitRatio = 0.6;
|
||||||
|
private Integer checkSize = 10;
|
||||||
|
private Double imageScalingFactor = 0.7;
|
||||||
|
private String copyrightAddresses = "www.zxxk.com,www.zujuan.com,www.jyeoo.com";
|
||||||
|
private Boolean newParseVersion;
|
||||||
|
private Boolean newDownloadVersion;
|
||||||
|
private Boolean openParseCache = true;
|
||||||
|
private Map<String, List<String>> cleanStyleMap = new HashMap<String, List<String>>();
|
||||||
|
private String transformScriptPath = "E:\\IdeaProject\\jby-project\\base_server\\jby-office\\jby-office-transform";
|
||||||
|
|
||||||
|
public String getTransformScriptPath() {
|
||||||
|
return this.isLocal() ? this.transformScriptPath : "/opt/servers/jby-office-transform";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPythonCommand() {
|
||||||
|
return this.isLocal() ? "python" : "python3";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLocal() {
|
||||||
|
String env = SpringContextHolder.getActiveProfile();
|
||||||
|
return StringUtils.isBlank((CharSequence)env) || env.contains("local");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTmpPath() {
|
||||||
|
return this.tmpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTemplatePath() {
|
||||||
|
return this.templatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getDeleteTmpFile() {
|
||||||
|
return this.deleteTmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getForeLatexToMathMlApi() {
|
||||||
|
return this.foreLatexToMathMlApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLatexToMathMlApi() {
|
||||||
|
return this.latexToMathMlApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMathMlToLatexApi() {
|
||||||
|
return this.mathMlToLatexApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getMathMlToLatexApiSwitch() {
|
||||||
|
return this.mathMlToLatexApiSwitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMf2SvgApi() {
|
||||||
|
return this.mf2SvgApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDownloadPdfApi() {
|
||||||
|
return this.downloadPdfApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorBookTemplate() {
|
||||||
|
return this.errorBookTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNodeConnectTimeout() {
|
||||||
|
return this.nodeConnectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getNodeSocketTimeout() {
|
||||||
|
return this.nodeSocketTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getContentLimit() {
|
||||||
|
return this.contentLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getLimitRatio() {
|
||||||
|
return this.limitRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCheckSize() {
|
||||||
|
return this.checkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getImageScalingFactor() {
|
||||||
|
return this.imageScalingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCopyrightAddresses() {
|
||||||
|
return this.copyrightAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewParseVersion() {
|
||||||
|
return this.newParseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewDownloadVersion() {
|
||||||
|
return this.newDownloadVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getOpenParseCache() {
|
||||||
|
return this.openParseCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getCleanStyleMap() {
|
||||||
|
return this.cleanStyleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTmpPath(String tmpPath) {
|
||||||
|
this.tmpPath = tmpPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTemplatePath(String templatePath) {
|
||||||
|
this.templatePath = templatePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeleteTmpFile(Boolean deleteTmpFile) {
|
||||||
|
this.deleteTmpFile = deleteTmpFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setForeLatexToMathMlApi(String foreLatexToMathMlApi) {
|
||||||
|
this.foreLatexToMathMlApi = foreLatexToMathMlApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLatexToMathMlApi(String latexToMathMlApi) {
|
||||||
|
this.latexToMathMlApi = latexToMathMlApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMathMlToLatexApi(String mathMlToLatexApi) {
|
||||||
|
this.mathMlToLatexApi = mathMlToLatexApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMathMlToLatexApiSwitch(Boolean mathMlToLatexApiSwitch) {
|
||||||
|
this.mathMlToLatexApiSwitch = mathMlToLatexApiSwitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMf2SvgApi(String mf2SvgApi) {
|
||||||
|
this.mf2SvgApi = mf2SvgApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownloadPdfApi(String downloadPdfApi) {
|
||||||
|
this.downloadPdfApi = downloadPdfApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setErrorBookTemplate(String errorBookTemplate) {
|
||||||
|
this.errorBookTemplate = errorBookTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeConnectTimeout(Integer nodeConnectTimeout) {
|
||||||
|
this.nodeConnectTimeout = nodeConnectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNodeSocketTimeout(Integer nodeSocketTimeout) {
|
||||||
|
this.nodeSocketTimeout = nodeSocketTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContentLimit(Long contentLimit) {
|
||||||
|
this.contentLimit = contentLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLimitRatio(Double limitRatio) {
|
||||||
|
this.limitRatio = limitRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckSize(Integer checkSize) {
|
||||||
|
this.checkSize = checkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageScalingFactor(Double imageScalingFactor) {
|
||||||
|
this.imageScalingFactor = imageScalingFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCopyrightAddresses(String copyrightAddresses) {
|
||||||
|
this.copyrightAddresses = copyrightAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewParseVersion(Boolean newParseVersion) {
|
||||||
|
this.newParseVersion = newParseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewDownloadVersion(Boolean newDownloadVersion) {
|
||||||
|
this.newDownloadVersion = newDownloadVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOpenParseCache(Boolean openParseCache) {
|
||||||
|
this.openParseCache = openParseCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCleanStyleMap(Map<String, List<String>> cleanStyleMap) {
|
||||||
|
this.cleanStyleMap = cleanStyleMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransformScriptPath(String transformScriptPath) {
|
||||||
|
this.transformScriptPath = transformScriptPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof OfficeCoreConfig)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OfficeCoreConfig other = (OfficeCoreConfig)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$tmpPath = this.getTmpPath();
|
||||||
|
String other$tmpPath = other.getTmpPath();
|
||||||
|
if (this$tmpPath == null ? other$tmpPath != null : !this$tmpPath.equals(other$tmpPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$templatePath = this.getTemplatePath();
|
||||||
|
String other$templatePath = other.getTemplatePath();
|
||||||
|
if (this$templatePath == null ? other$templatePath != null : !this$templatePath.equals(other$templatePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$deleteTmpFile = this.getDeleteTmpFile();
|
||||||
|
Boolean other$deleteTmpFile = other.getDeleteTmpFile();
|
||||||
|
if (this$deleteTmpFile == null ? other$deleteTmpFile != null : !((Object)this$deleteTmpFile).equals(other$deleteTmpFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$foreLatexToMathMlApi = this.getForeLatexToMathMlApi();
|
||||||
|
String other$foreLatexToMathMlApi = other.getForeLatexToMathMlApi();
|
||||||
|
if (this$foreLatexToMathMlApi == null ? other$foreLatexToMathMlApi != null : !this$foreLatexToMathMlApi.equals(other$foreLatexToMathMlApi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$latexToMathMlApi = this.getLatexToMathMlApi();
|
||||||
|
String other$latexToMathMlApi = other.getLatexToMathMlApi();
|
||||||
|
if (this$latexToMathMlApi == null ? other$latexToMathMlApi != null : !this$latexToMathMlApi.equals(other$latexToMathMlApi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$mathMlToLatexApi = this.getMathMlToLatexApi();
|
||||||
|
String other$mathMlToLatexApi = other.getMathMlToLatexApi();
|
||||||
|
if (this$mathMlToLatexApi == null ? other$mathMlToLatexApi != null : !this$mathMlToLatexApi.equals(other$mathMlToLatexApi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$mathMlToLatexApiSwitch = this.getMathMlToLatexApiSwitch();
|
||||||
|
Boolean other$mathMlToLatexApiSwitch = other.getMathMlToLatexApiSwitch();
|
||||||
|
if (this$mathMlToLatexApiSwitch == null ? other$mathMlToLatexApiSwitch != null : !((Object)this$mathMlToLatexApiSwitch).equals(other$mathMlToLatexApiSwitch)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$mf2SvgApi = this.getMf2SvgApi();
|
||||||
|
String other$mf2SvgApi = other.getMf2SvgApi();
|
||||||
|
if (this$mf2SvgApi == null ? other$mf2SvgApi != null : !this$mf2SvgApi.equals(other$mf2SvgApi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$downloadPdfApi = this.getDownloadPdfApi();
|
||||||
|
String other$downloadPdfApi = other.getDownloadPdfApi();
|
||||||
|
if (this$downloadPdfApi == null ? other$downloadPdfApi != null : !this$downloadPdfApi.equals(other$downloadPdfApi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$errorBookTemplate = this.getErrorBookTemplate();
|
||||||
|
String other$errorBookTemplate = other.getErrorBookTemplate();
|
||||||
|
if (this$errorBookTemplate == null ? other$errorBookTemplate != null : !this$errorBookTemplate.equals(other$errorBookTemplate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$nodeConnectTimeout = this.getNodeConnectTimeout();
|
||||||
|
Integer other$nodeConnectTimeout = other.getNodeConnectTimeout();
|
||||||
|
if (this$nodeConnectTimeout == null ? other$nodeConnectTimeout != null : !((Object)this$nodeConnectTimeout).equals(other$nodeConnectTimeout)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$nodeSocketTimeout = this.getNodeSocketTimeout();
|
||||||
|
Integer other$nodeSocketTimeout = other.getNodeSocketTimeout();
|
||||||
|
if (this$nodeSocketTimeout == null ? other$nodeSocketTimeout != null : !((Object)this$nodeSocketTimeout).equals(other$nodeSocketTimeout)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Long this$contentLimit = this.getContentLimit();
|
||||||
|
Long other$contentLimit = other.getContentLimit();
|
||||||
|
if (this$contentLimit == null ? other$contentLimit != null : !((Object)this$contentLimit).equals(other$contentLimit)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$limitRatio = this.getLimitRatio();
|
||||||
|
Double other$limitRatio = other.getLimitRatio();
|
||||||
|
if (this$limitRatio == null ? other$limitRatio != null : !((Object)this$limitRatio).equals(other$limitRatio)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$checkSize = this.getCheckSize();
|
||||||
|
Integer other$checkSize = other.getCheckSize();
|
||||||
|
if (this$checkSize == null ? other$checkSize != null : !((Object)this$checkSize).equals(other$checkSize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$imageScalingFactor = this.getImageScalingFactor();
|
||||||
|
Double other$imageScalingFactor = other.getImageScalingFactor();
|
||||||
|
if (this$imageScalingFactor == null ? other$imageScalingFactor != null : !((Object)this$imageScalingFactor).equals(other$imageScalingFactor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$copyrightAddresses = this.getCopyrightAddresses();
|
||||||
|
String other$copyrightAddresses = other.getCopyrightAddresses();
|
||||||
|
if (this$copyrightAddresses == null ? other$copyrightAddresses != null : !this$copyrightAddresses.equals(other$copyrightAddresses)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$newParseVersion = this.getNewParseVersion();
|
||||||
|
Boolean other$newParseVersion = other.getNewParseVersion();
|
||||||
|
if (this$newParseVersion == null ? other$newParseVersion != null : !((Object)this$newParseVersion).equals(other$newParseVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$newDownloadVersion = this.getNewDownloadVersion();
|
||||||
|
Boolean other$newDownloadVersion = other.getNewDownloadVersion();
|
||||||
|
if (this$newDownloadVersion == null ? other$newDownloadVersion != null : !((Object)this$newDownloadVersion).equals(other$newDownloadVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$openParseCache = this.getOpenParseCache();
|
||||||
|
Boolean other$openParseCache = other.getOpenParseCache();
|
||||||
|
if (this$openParseCache == null ? other$openParseCache != null : !((Object)this$openParseCache).equals(other$openParseCache)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, List<String>> this$cleanStyleMap = this.getCleanStyleMap();
|
||||||
|
Map<String, List<String>> other$cleanStyleMap = other.getCleanStyleMap();
|
||||||
|
if (this$cleanStyleMap == null ? other$cleanStyleMap != null : !((Object)this$cleanStyleMap).equals(other$cleanStyleMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$transformScriptPath = this.getTransformScriptPath();
|
||||||
|
String other$transformScriptPath = other.getTransformScriptPath();
|
||||||
|
return !(this$transformScriptPath == null ? other$transformScriptPath != null : !this$transformScriptPath.equals(other$transformScriptPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof OfficeCoreConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
String $tmpPath = this.getTmpPath();
|
||||||
|
result = result * 59 + ($tmpPath == null ? 43 : $tmpPath.hashCode());
|
||||||
|
String $templatePath = this.getTemplatePath();
|
||||||
|
result = result * 59 + ($templatePath == null ? 43 : $templatePath.hashCode());
|
||||||
|
Boolean $deleteTmpFile = this.getDeleteTmpFile();
|
||||||
|
result = result * 59 + ($deleteTmpFile == null ? 43 : ((Object)$deleteTmpFile).hashCode());
|
||||||
|
String $foreLatexToMathMlApi = this.getForeLatexToMathMlApi();
|
||||||
|
result = result * 59 + ($foreLatexToMathMlApi == null ? 43 : $foreLatexToMathMlApi.hashCode());
|
||||||
|
String $latexToMathMlApi = this.getLatexToMathMlApi();
|
||||||
|
result = result * 59 + ($latexToMathMlApi == null ? 43 : $latexToMathMlApi.hashCode());
|
||||||
|
String $mathMlToLatexApi = this.getMathMlToLatexApi();
|
||||||
|
result = result * 59 + ($mathMlToLatexApi == null ? 43 : $mathMlToLatexApi.hashCode());
|
||||||
|
Boolean $mathMlToLatexApiSwitch = this.getMathMlToLatexApiSwitch();
|
||||||
|
result = result * 59 + ($mathMlToLatexApiSwitch == null ? 43 : ((Object)$mathMlToLatexApiSwitch).hashCode());
|
||||||
|
String $mf2SvgApi = this.getMf2SvgApi();
|
||||||
|
result = result * 59 + ($mf2SvgApi == null ? 43 : $mf2SvgApi.hashCode());
|
||||||
|
String $downloadPdfApi = this.getDownloadPdfApi();
|
||||||
|
result = result * 59 + ($downloadPdfApi == null ? 43 : $downloadPdfApi.hashCode());
|
||||||
|
String $errorBookTemplate = this.getErrorBookTemplate();
|
||||||
|
result = result * 59 + ($errorBookTemplate == null ? 43 : $errorBookTemplate.hashCode());
|
||||||
|
Integer $nodeConnectTimeout = this.getNodeConnectTimeout();
|
||||||
|
result = result * 59 + ($nodeConnectTimeout == null ? 43 : ((Object)$nodeConnectTimeout).hashCode());
|
||||||
|
Integer $nodeSocketTimeout = this.getNodeSocketTimeout();
|
||||||
|
result = result * 59 + ($nodeSocketTimeout == null ? 43 : ((Object)$nodeSocketTimeout).hashCode());
|
||||||
|
Long $contentLimit = this.getContentLimit();
|
||||||
|
result = result * 59 + ($contentLimit == null ? 43 : ((Object)$contentLimit).hashCode());
|
||||||
|
Double $limitRatio = this.getLimitRatio();
|
||||||
|
result = result * 59 + ($limitRatio == null ? 43 : ((Object)$limitRatio).hashCode());
|
||||||
|
Integer $checkSize = this.getCheckSize();
|
||||||
|
result = result * 59 + ($checkSize == null ? 43 : ((Object)$checkSize).hashCode());
|
||||||
|
Double $imageScalingFactor = this.getImageScalingFactor();
|
||||||
|
result = result * 59 + ($imageScalingFactor == null ? 43 : ((Object)$imageScalingFactor).hashCode());
|
||||||
|
String $copyrightAddresses = this.getCopyrightAddresses();
|
||||||
|
result = result * 59 + ($copyrightAddresses == null ? 43 : $copyrightAddresses.hashCode());
|
||||||
|
Boolean $newParseVersion = this.getNewParseVersion();
|
||||||
|
result = result * 59 + ($newParseVersion == null ? 43 : ((Object)$newParseVersion).hashCode());
|
||||||
|
Boolean $newDownloadVersion = this.getNewDownloadVersion();
|
||||||
|
result = result * 59 + ($newDownloadVersion == null ? 43 : ((Object)$newDownloadVersion).hashCode());
|
||||||
|
Boolean $openParseCache = this.getOpenParseCache();
|
||||||
|
result = result * 59 + ($openParseCache == null ? 43 : ((Object)$openParseCache).hashCode());
|
||||||
|
Map<String, List<String>> $cleanStyleMap = this.getCleanStyleMap();
|
||||||
|
result = result * 59 + ($cleanStyleMap == null ? 43 : ((Object)$cleanStyleMap).hashCode());
|
||||||
|
String $transformScriptPath = this.getTransformScriptPath();
|
||||||
|
result = result * 59 + ($transformScriptPath == null ? 43 : $transformScriptPath.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "OfficeCoreConfig(tmpPath=" + this.getTmpPath() + ", templatePath=" + this.getTemplatePath() + ", deleteTmpFile=" + this.getDeleteTmpFile() + ", foreLatexToMathMlApi=" + this.getForeLatexToMathMlApi() + ", latexToMathMlApi=" + this.getLatexToMathMlApi() + ", mathMlToLatexApi=" + this.getMathMlToLatexApi() + ", mathMlToLatexApiSwitch=" + this.getMathMlToLatexApiSwitch() + ", mf2SvgApi=" + this.getMf2SvgApi() + ", downloadPdfApi=" + this.getDownloadPdfApi() + ", errorBookTemplate=" + this.getErrorBookTemplate() + ", nodeConnectTimeout=" + this.getNodeConnectTimeout() + ", nodeSocketTimeout=" + this.getNodeSocketTimeout() + ", contentLimit=" + this.getContentLimit() + ", limitRatio=" + this.getLimitRatio() + ", checkSize=" + this.getCheckSize() + ", imageScalingFactor=" + this.getImageScalingFactor() + ", copyrightAddresses=" + this.getCopyrightAddresses() + ", newParseVersion=" + this.getNewParseVersion() + ", newDownloadVersion=" + this.getNewDownloadVersion() + ", openParseCache=" + this.getOpenParseCache() + ", cleanStyleMap=" + this.getCleanStyleMap() + ", transformScriptPath=" + this.getTransformScriptPath() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
152
java-src/controller/FileController.java
Normal file
152
java-src/controller/FileController.java
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.fastjson.JSON
|
||||||
|
* com.jby.common.advisor.annotation.ParamBarrier
|
||||||
|
* com.jby.common.result.Result
|
||||||
|
* com.jby.office.api.FileServiceApi
|
||||||
|
* com.jby.office.domain.param.DownloadFileParam
|
||||||
|
* com.jby.office.domain.param.DownloadParam
|
||||||
|
* com.jby.office.domain.param.FileParam
|
||||||
|
* com.jby.office.domain.param.GenerateFileParam
|
||||||
|
* com.jby.office.domain.param.OfficeFileParseParam
|
||||||
|
* com.jby.office.domain.param.OfficeFileParseRecordParam
|
||||||
|
* com.jby.office.domain.vo.OfficeFileDownloadScheduleVO
|
||||||
|
* com.jby.office.domain.vo.OfficeFileInfoVO
|
||||||
|
* com.jby.office.domain.vo.OfficeFileParseRecordVO
|
||||||
|
* com.jby.office.domain.vo.OfficeFileParseVO
|
||||||
|
* com.jby.officecore.annotation.CostTime
|
||||||
|
* com.jby.officecore.domain.dto.CreateWordDTO
|
||||||
|
* com.jby.officecore.utils.WordExportUtil
|
||||||
|
* com.jby.system.domain.param.NodeParam
|
||||||
|
* javax.annotation.Resource
|
||||||
|
* org.map.utils.BeanMapping
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
* org.springframework.web.bind.annotation.GetMapping
|
||||||
|
* org.springframework.web.bind.annotation.PathVariable
|
||||||
|
* org.springframework.web.bind.annotation.PostMapping
|
||||||
|
* org.springframework.web.bind.annotation.RequestBody
|
||||||
|
* org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
* org.springframework.web.bind.annotation.RestController
|
||||||
|
*/
|
||||||
|
package com.jby.officefile.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.jby.common.advisor.annotation.ParamBarrier;
|
||||||
|
import com.jby.common.result.Result;
|
||||||
|
import com.jby.office.api.FileServiceApi;
|
||||||
|
import com.jby.office.domain.param.DownloadFileParam;
|
||||||
|
import com.jby.office.domain.param.DownloadParam;
|
||||||
|
import com.jby.office.domain.param.FileParam;
|
||||||
|
import com.jby.office.domain.param.GenerateFileParam;
|
||||||
|
import com.jby.office.domain.param.OfficeFileParseParam;
|
||||||
|
import com.jby.office.domain.param.OfficeFileParseRecordParam;
|
||||||
|
import com.jby.office.domain.vo.OfficeFileDownloadScheduleVO;
|
||||||
|
import com.jby.office.domain.vo.OfficeFileInfoVO;
|
||||||
|
import com.jby.office.domain.vo.OfficeFileParseRecordVO;
|
||||||
|
import com.jby.office.domain.vo.OfficeFileParseVO;
|
||||||
|
import com.jby.officecore.annotation.CostTime;
|
||||||
|
import com.jby.officecore.domain.dto.CreateWordDTO;
|
||||||
|
import com.jby.officecore.utils.WordExportUtil;
|
||||||
|
import com.jby.officefile.service.FileService;
|
||||||
|
import com.jby.system.domain.param.NodeParam;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import org.map.utils.BeanMapping;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value={"/file"})
|
||||||
|
public class FileController
|
||||||
|
implements FileServiceApi {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(FileController.class);
|
||||||
|
@Resource
|
||||||
|
private FileService fileService;
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/generate/by/url"})
|
||||||
|
public Result<OfficeFileInfoVO> generateFileByUrl(@RequestBody GenerateFileParam param) {
|
||||||
|
log.info("\u901a\u8fc7word\u8d44\u6e90\u5730\u5740\u751f\u6210\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6(\u5b8c\u6574\u6587\u6863), param={}", (Object)JSON.toJSONString((Object)param));
|
||||||
|
OfficeFileInfoVO result = this.fileService.generateFileByUrl(param);
|
||||||
|
return Result.success((Object)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/generate/by/file"}, consumes={"multipart/form-data"})
|
||||||
|
public Result<OfficeFileInfoVO> generateFileByFile(FileParam param) {
|
||||||
|
log.info("\u901a\u8fc7word\u8d44\u6e90\u6587\u4ef6\u751f\u6210\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6(\u5b8c\u6574\u6587\u6863), param={}", (Object)JSON.toJSONString((Object)param));
|
||||||
|
OfficeFileInfoVO result = this.fileService.generateFileByFile(param);
|
||||||
|
return Result.success((Object)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/parse"}, consumes={"multipart/form-data"})
|
||||||
|
public Result<OfficeFileParseVO> parseOfficeFile(OfficeFileParseParam param) {
|
||||||
|
OfficeFileParseVO parseVO = this.fileService.parseOfficeFile(param);
|
||||||
|
return Result.success((Object)parseVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/download"}, consumes={"multipart/form-data"})
|
||||||
|
public Result<String> downloadOfficeFile(DownloadFileParam param) {
|
||||||
|
log.info("\u4e0b\u8f7d\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6, file={}, param={}", (Object)Objects.nonNull(param.getPdfFile()), (Object)JSON.toJSONString((Object)param));
|
||||||
|
String fileUrl = this.fileService.downloadOfficeFile(param);
|
||||||
|
return Result.success((Object)fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/v2/download"})
|
||||||
|
public Result<String> downloadFile(@RequestBody DownloadParam param) {
|
||||||
|
log.info("\u4e0b\u8f7d\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6V2, param={}", (Object)JSON.toJSONString((Object)param));
|
||||||
|
DownloadFileParam fileParam = (DownloadFileParam)BeanMapping.toBean((Object)param, DownloadFileParam.class);
|
||||||
|
String fileUrl = this.fileService.downloadOfficeFile(fileParam);
|
||||||
|
return Result.success((Object)fileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/batch/download"})
|
||||||
|
public Result<OfficeFileDownloadScheduleVO> batchDownloadOfficeFile(@RequestBody NodeParam rootNode) {
|
||||||
|
log.info("\u6279\u91cf\u4e0b\u8f7d\u5728\u7ebf\u6587\u4ef6, \u8282\u70b9\u53c2\u6570rootNode={}", (Object)JSON.toJSONString((Object)rootNode));
|
||||||
|
OfficeFileDownloadScheduleVO result = this.fileService.batchDownloadOfficeFile(rootNode);
|
||||||
|
return Result.success((Object)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CostTime
|
||||||
|
@PostMapping(value={"/download/word"})
|
||||||
|
public Result<String> downloadWord(@RequestBody CreateWordDTO param) {
|
||||||
|
log.info("\u4e0b\u8f7dword\u6587\u4ef6, param={}", (Object)JSON.toJSONString((Object)param));
|
||||||
|
String result = WordExportUtil.createWord((CreateWordDTO)param);
|
||||||
|
return Result.success((Object)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value={"/parse/record/{md5}"})
|
||||||
|
public Result<OfficeFileParseRecordVO> getOfficeFileParseRecordByMd5(@PathVariable String md5) {
|
||||||
|
OfficeFileParseRecordVO result = this.fileService.getOfficeFileParseRecordByMd5(md5);
|
||||||
|
return Result.success((Object)result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value={"/parse/record"})
|
||||||
|
public Result<?> saveOfficeFileParseRecord(@RequestBody OfficeFileParseRecordParam param) {
|
||||||
|
this.fileService.saveOfficeFileParseRecord(param);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value={"/parse/record/query"})
|
||||||
|
@ParamBarrier(value={"fileIds"})
|
||||||
|
public Result<List<OfficeFileParseRecordVO>> queryFileUrlByFileIds(@RequestBody OfficeFileParseRecordParam param) {
|
||||||
|
List<OfficeFileParseRecordVO> result = this.fileService.queryFileUrlByFileIds(param);
|
||||||
|
return Result.success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
123
java-src/domain/bo/ImageBO.java
Normal file
123
java-src/domain/bo/ImageBO.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.domain.bo;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class ImageBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = 6640950035295880234L;
|
||||||
|
private InputStream is;
|
||||||
|
private Double width;
|
||||||
|
private Double height;
|
||||||
|
private Integer pictureType;
|
||||||
|
private String pictureUrl;
|
||||||
|
|
||||||
|
public InputStream getIs() {
|
||||||
|
return this.is;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getHeight() {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPictureType() {
|
||||||
|
return this.pictureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPictureUrl() {
|
||||||
|
return this.pictureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setIs(InputStream is) {
|
||||||
|
this.is = is;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setWidth(Double width) {
|
||||||
|
this.width = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setHeight(Double height) {
|
||||||
|
this.height = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setPictureType(Integer pictureType) {
|
||||||
|
this.pictureType = pictureType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setPictureUrl(String pictureUrl) {
|
||||||
|
this.pictureUrl = pictureUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ImageBO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ImageBO other = (ImageBO)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InputStream this$is = this.getIs();
|
||||||
|
InputStream other$is = other.getIs();
|
||||||
|
if (this$is == null ? other$is != null : !this$is.equals(other$is)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$width = this.getWidth();
|
||||||
|
Double other$width = other.getWidth();
|
||||||
|
if (this$width == null ? other$width != null : !((Object)this$width).equals(other$width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$height = this.getHeight();
|
||||||
|
Double other$height = other.getHeight();
|
||||||
|
if (this$height == null ? other$height != null : !((Object)this$height).equals(other$height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$pictureType = this.getPictureType();
|
||||||
|
Integer other$pictureType = other.getPictureType();
|
||||||
|
if (this$pictureType == null ? other$pictureType != null : !((Object)this$pictureType).equals(other$pictureType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$pictureUrl = this.getPictureUrl();
|
||||||
|
String other$pictureUrl = other.getPictureUrl();
|
||||||
|
return !(this$pictureUrl == null ? other$pictureUrl != null : !this$pictureUrl.equals(other$pictureUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof ImageBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
InputStream $is = this.getIs();
|
||||||
|
result = result * 59 + ($is == null ? 43 : $is.hashCode());
|
||||||
|
Double $width = this.getWidth();
|
||||||
|
result = result * 59 + ($width == null ? 43 : ((Object)$width).hashCode());
|
||||||
|
Double $height = this.getHeight();
|
||||||
|
result = result * 59 + ($height == null ? 43 : ((Object)$height).hashCode());
|
||||||
|
Integer $pictureType = this.getPictureType();
|
||||||
|
result = result * 59 + ($pictureType == null ? 43 : ((Object)$pictureType).hashCode());
|
||||||
|
String $pictureUrl = this.getPictureUrl();
|
||||||
|
result = result * 59 + ($pictureUrl == null ? 43 : $pictureUrl.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ImageBO(is=" + this.getIs() + ", width=" + this.getWidth() + ", height=" + this.getHeight() + ", pictureType=" + this.getPictureType() + ", pictureUrl=" + this.getPictureUrl() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
455
java-src/domain/bo/ParseBO.java
Normal file
455
java-src/domain/bo/ParseBO.java
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.domain.bo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.jby.officecore.domain.bo.ImageBO;
|
||||||
|
import com.jby.officecore.utils.NumPr;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
|
||||||
|
|
||||||
|
public class ParseBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = -1386361606482288851L;
|
||||||
|
public static final String IMAGES = "images";
|
||||||
|
public static final String PARSE_ = "parse-";
|
||||||
|
public static final String OUTPUT_HTML = "output.html";
|
||||||
|
private static final String JBY_OPEN_OFFICE_XML_ = "JBY_OPEN_OFFICE_XML_";
|
||||||
|
private static final String JBY_IMAGE_DATA_ = "JBY_IMAGE_DATA_";
|
||||||
|
private static final String PLACEHOLDER_SUFFIX = "_END";
|
||||||
|
private static final String IMAGE_ = "image_";
|
||||||
|
private static final String JBY_EQ_LATEX_ = "JBY_EQ_LATEX_";
|
||||||
|
public static final String JBY_TEXTBOX = "JBY_TEXTBOX_JBY";
|
||||||
|
private static final String JBY_MATHTYPE_DATA_ = "JBY_MATHTYPE_DATA_";
|
||||||
|
private Path tempDir;
|
||||||
|
private String sourceFilePath;
|
||||||
|
private String sourceFileUrl;
|
||||||
|
private String sourceFileMd5;
|
||||||
|
private String sourceFileBakPath;
|
||||||
|
private String htmlPath;
|
||||||
|
private String imagesDir;
|
||||||
|
private String ossFolder;
|
||||||
|
private Transformer transformer;
|
||||||
|
private List<JSONObject> latexReplaceList;
|
||||||
|
private Map<String, String> imgMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> shapeImgMap;
|
||||||
|
private Map<String, String> openOfficeXmlMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> mathTypeMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> eqLatexMap = new HashMap<String, String>();
|
||||||
|
private StyleDefinitionsPart styleDefinitionsPart;
|
||||||
|
private Map<String, NumPr> numberingDefinitionsMap = new HashMap<String, NumPr>();
|
||||||
|
private Map<String, ImageBO> imageMap;
|
||||||
|
private Boolean newParseVersion;
|
||||||
|
private Integer count = 0;
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
public ParseBO() {
|
||||||
|
this.shapeImgMap = new HashMap<String, String>();
|
||||||
|
this.imageMap = new HashMap<String, ImageBO>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer incrCount() {
|
||||||
|
this.count = this.count + 1;
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOpenOfficeXmlPlaceholder() {
|
||||||
|
return JBY_OPEN_OFFICE_XML_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMathTypePlaceholder() {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMathTypePlaceholder(String ctOleObjectRid) {
|
||||||
|
return JBY_MATHTYPE_DATA_ + ctOleObjectRid + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImgName() {
|
||||||
|
return IMAGE_ + this.incrCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEqLatexPlaceholder() {
|
||||||
|
return JBY_EQ_LATEX_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShapeImgPlaceholder() {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImagePlaceholder(String rid) {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getTempDir() {
|
||||||
|
return this.tempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFilePath() {
|
||||||
|
return this.sourceFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileUrl() {
|
||||||
|
return this.sourceFileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileMd5() {
|
||||||
|
return this.sourceFileMd5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileBakPath() {
|
||||||
|
return this.sourceFileBakPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHtmlPath() {
|
||||||
|
return this.htmlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImagesDir() {
|
||||||
|
return this.imagesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOssFolder() {
|
||||||
|
return this.ossFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformer getTransformer() {
|
||||||
|
return this.transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JSONObject> getLatexReplaceList() {
|
||||||
|
return this.latexReplaceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getImgMap() {
|
||||||
|
return this.imgMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getShapeImgMap() {
|
||||||
|
return this.shapeImgMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getOpenOfficeXmlMap() {
|
||||||
|
return this.openOfficeXmlMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMathTypeMap() {
|
||||||
|
return this.mathTypeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEqLatexMap() {
|
||||||
|
return this.eqLatexMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleDefinitionsPart getStyleDefinitionsPart() {
|
||||||
|
return this.styleDefinitionsPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, NumPr> getNumberingDefinitionsMap() {
|
||||||
|
return this.numberingDefinitionsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ImageBO> getImageMap() {
|
||||||
|
return this.imageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewParseVersion() {
|
||||||
|
return this.newParseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCount() {
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFileId() {
|
||||||
|
return this.fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setTempDir(Path tempDir) {
|
||||||
|
this.tempDir = tempDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFilePath(String sourceFilePath) {
|
||||||
|
this.sourceFilePath = sourceFilePath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileUrl(String sourceFileUrl) {
|
||||||
|
this.sourceFileUrl = sourceFileUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileMd5(String sourceFileMd5) {
|
||||||
|
this.sourceFileMd5 = sourceFileMd5;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileBakPath(String sourceFileBakPath) {
|
||||||
|
this.sourceFileBakPath = sourceFileBakPath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setHtmlPath(String htmlPath) {
|
||||||
|
this.htmlPath = htmlPath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImagesDir(String imagesDir) {
|
||||||
|
this.imagesDir = imagesDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setOssFolder(String ossFolder) {
|
||||||
|
this.ossFolder = ossFolder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setTransformer(Transformer transformer) {
|
||||||
|
this.transformer = transformer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setLatexReplaceList(List<JSONObject> latexReplaceList) {
|
||||||
|
this.latexReplaceList = latexReplaceList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImgMap(Map<String, String> imgMap) {
|
||||||
|
this.imgMap = imgMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setShapeImgMap(Map<String, String> shapeImgMap) {
|
||||||
|
this.shapeImgMap = shapeImgMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setOpenOfficeXmlMap(Map<String, String> openOfficeXmlMap) {
|
||||||
|
this.openOfficeXmlMap = openOfficeXmlMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setMathTypeMap(Map<String, String> mathTypeMap) {
|
||||||
|
this.mathTypeMap = mathTypeMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setEqLatexMap(Map<String, String> eqLatexMap) {
|
||||||
|
this.eqLatexMap = eqLatexMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setStyleDefinitionsPart(StyleDefinitionsPart styleDefinitionsPart) {
|
||||||
|
this.styleDefinitionsPart = styleDefinitionsPart;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setNumberingDefinitionsMap(Map<String, NumPr> numberingDefinitionsMap) {
|
||||||
|
this.numberingDefinitionsMap = numberingDefinitionsMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImageMap(Map<String, ImageBO> imageMap) {
|
||||||
|
this.imageMap = imageMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setNewParseVersion(Boolean newParseVersion) {
|
||||||
|
this.newParseVersion = newParseVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setCount(Integer count) {
|
||||||
|
this.count = count;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setFileId(Long fileId) {
|
||||||
|
this.fileId = fileId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ParseBO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ParseBO other = (ParseBO)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Path this$tempDir = this.getTempDir();
|
||||||
|
Path other$tempDir = other.getTempDir();
|
||||||
|
if (this$tempDir == null ? other$tempDir != null : !((Object)this$tempDir).equals(other$tempDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFilePath = this.getSourceFilePath();
|
||||||
|
String other$sourceFilePath = other.getSourceFilePath();
|
||||||
|
if (this$sourceFilePath == null ? other$sourceFilePath != null : !this$sourceFilePath.equals(other$sourceFilePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileUrl = this.getSourceFileUrl();
|
||||||
|
String other$sourceFileUrl = other.getSourceFileUrl();
|
||||||
|
if (this$sourceFileUrl == null ? other$sourceFileUrl != null : !this$sourceFileUrl.equals(other$sourceFileUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileMd5 = this.getSourceFileMd5();
|
||||||
|
String other$sourceFileMd5 = other.getSourceFileMd5();
|
||||||
|
if (this$sourceFileMd5 == null ? other$sourceFileMd5 != null : !this$sourceFileMd5.equals(other$sourceFileMd5)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileBakPath = this.getSourceFileBakPath();
|
||||||
|
String other$sourceFileBakPath = other.getSourceFileBakPath();
|
||||||
|
if (this$sourceFileBakPath == null ? other$sourceFileBakPath != null : !this$sourceFileBakPath.equals(other$sourceFileBakPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$htmlPath = this.getHtmlPath();
|
||||||
|
String other$htmlPath = other.getHtmlPath();
|
||||||
|
if (this$htmlPath == null ? other$htmlPath != null : !this$htmlPath.equals(other$htmlPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$imagesDir = this.getImagesDir();
|
||||||
|
String other$imagesDir = other.getImagesDir();
|
||||||
|
if (this$imagesDir == null ? other$imagesDir != null : !this$imagesDir.equals(other$imagesDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$ossFolder = this.getOssFolder();
|
||||||
|
String other$ossFolder = other.getOssFolder();
|
||||||
|
if (this$ossFolder == null ? other$ossFolder != null : !this$ossFolder.equals(other$ossFolder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Transformer this$transformer = this.getTransformer();
|
||||||
|
Transformer other$transformer = other.getTransformer();
|
||||||
|
if (this$transformer == null ? other$transformer != null : !this$transformer.equals(other$transformer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<JSONObject> this$latexReplaceList = this.getLatexReplaceList();
|
||||||
|
List<JSONObject> other$latexReplaceList = other.getLatexReplaceList();
|
||||||
|
if (this$latexReplaceList == null ? other$latexReplaceList != null : !((Object)this$latexReplaceList).equals(other$latexReplaceList)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$imgMap = this.getImgMap();
|
||||||
|
Map<String, String> other$imgMap = other.getImgMap();
|
||||||
|
if (this$imgMap == null ? other$imgMap != null : !((Object)this$imgMap).equals(other$imgMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$shapeImgMap = this.getShapeImgMap();
|
||||||
|
Map<String, String> other$shapeImgMap = other.getShapeImgMap();
|
||||||
|
if (this$shapeImgMap == null ? other$shapeImgMap != null : !((Object)this$shapeImgMap).equals(other$shapeImgMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$openOfficeXmlMap = this.getOpenOfficeXmlMap();
|
||||||
|
Map<String, String> other$openOfficeXmlMap = other.getOpenOfficeXmlMap();
|
||||||
|
if (this$openOfficeXmlMap == null ? other$openOfficeXmlMap != null : !((Object)this$openOfficeXmlMap).equals(other$openOfficeXmlMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$mathTypeMap = this.getMathTypeMap();
|
||||||
|
Map<String, String> other$mathTypeMap = other.getMathTypeMap();
|
||||||
|
if (this$mathTypeMap == null ? other$mathTypeMap != null : !((Object)this$mathTypeMap).equals(other$mathTypeMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$eqLatexMap = this.getEqLatexMap();
|
||||||
|
Map<String, String> other$eqLatexMap = other.getEqLatexMap();
|
||||||
|
if (this$eqLatexMap == null ? other$eqLatexMap != null : !((Object)this$eqLatexMap).equals(other$eqLatexMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StyleDefinitionsPart this$styleDefinitionsPart = this.getStyleDefinitionsPart();
|
||||||
|
StyleDefinitionsPart other$styleDefinitionsPart = other.getStyleDefinitionsPart();
|
||||||
|
if (this$styleDefinitionsPart == null ? other$styleDefinitionsPart != null : !this$styleDefinitionsPart.equals(other$styleDefinitionsPart)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, NumPr> this$numberingDefinitionsMap = this.getNumberingDefinitionsMap();
|
||||||
|
Map<String, NumPr> other$numberingDefinitionsMap = other.getNumberingDefinitionsMap();
|
||||||
|
if (this$numberingDefinitionsMap == null ? other$numberingDefinitionsMap != null : !((Object)this$numberingDefinitionsMap).equals(other$numberingDefinitionsMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, ImageBO> this$imageMap = this.getImageMap();
|
||||||
|
Map<String, ImageBO> other$imageMap = other.getImageMap();
|
||||||
|
if (this$imageMap == null ? other$imageMap != null : !((Object)this$imageMap).equals(other$imageMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$newParseVersion = this.getNewParseVersion();
|
||||||
|
Boolean other$newParseVersion = other.getNewParseVersion();
|
||||||
|
if (this$newParseVersion == null ? other$newParseVersion != null : !((Object)this$newParseVersion).equals(other$newParseVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$count = this.getCount();
|
||||||
|
Integer other$count = other.getCount();
|
||||||
|
if (this$count == null ? other$count != null : !((Object)this$count).equals(other$count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Long this$fileId = this.getFileId();
|
||||||
|
Long other$fileId = other.getFileId();
|
||||||
|
return !(this$fileId == null ? other$fileId != null : !((Object)this$fileId).equals(other$fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof ParseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
Path $tempDir = this.getTempDir();
|
||||||
|
result = result * 59 + ($tempDir == null ? 43 : ((Object)$tempDir).hashCode());
|
||||||
|
String $sourceFilePath = this.getSourceFilePath();
|
||||||
|
result = result * 59 + ($sourceFilePath == null ? 43 : $sourceFilePath.hashCode());
|
||||||
|
String $sourceFileUrl = this.getSourceFileUrl();
|
||||||
|
result = result * 59 + ($sourceFileUrl == null ? 43 : $sourceFileUrl.hashCode());
|
||||||
|
String $sourceFileMd5 = this.getSourceFileMd5();
|
||||||
|
result = result * 59 + ($sourceFileMd5 == null ? 43 : $sourceFileMd5.hashCode());
|
||||||
|
String $sourceFileBakPath = this.getSourceFileBakPath();
|
||||||
|
result = result * 59 + ($sourceFileBakPath == null ? 43 : $sourceFileBakPath.hashCode());
|
||||||
|
String $htmlPath = this.getHtmlPath();
|
||||||
|
result = result * 59 + ($htmlPath == null ? 43 : $htmlPath.hashCode());
|
||||||
|
String $imagesDir = this.getImagesDir();
|
||||||
|
result = result * 59 + ($imagesDir == null ? 43 : $imagesDir.hashCode());
|
||||||
|
String $ossFolder = this.getOssFolder();
|
||||||
|
result = result * 59 + ($ossFolder == null ? 43 : $ossFolder.hashCode());
|
||||||
|
Transformer $transformer = this.getTransformer();
|
||||||
|
result = result * 59 + ($transformer == null ? 43 : $transformer.hashCode());
|
||||||
|
List<JSONObject> $latexReplaceList = this.getLatexReplaceList();
|
||||||
|
result = result * 59 + ($latexReplaceList == null ? 43 : ((Object)$latexReplaceList).hashCode());
|
||||||
|
Map<String, String> $imgMap = this.getImgMap();
|
||||||
|
result = result * 59 + ($imgMap == null ? 43 : ((Object)$imgMap).hashCode());
|
||||||
|
Map<String, String> $shapeImgMap = this.getShapeImgMap();
|
||||||
|
result = result * 59 + ($shapeImgMap == null ? 43 : ((Object)$shapeImgMap).hashCode());
|
||||||
|
Map<String, String> $openOfficeXmlMap = this.getOpenOfficeXmlMap();
|
||||||
|
result = result * 59 + ($openOfficeXmlMap == null ? 43 : ((Object)$openOfficeXmlMap).hashCode());
|
||||||
|
Map<String, String> $mathTypeMap = this.getMathTypeMap();
|
||||||
|
result = result * 59 + ($mathTypeMap == null ? 43 : ((Object)$mathTypeMap).hashCode());
|
||||||
|
Map<String, String> $eqLatexMap = this.getEqLatexMap();
|
||||||
|
result = result * 59 + ($eqLatexMap == null ? 43 : ((Object)$eqLatexMap).hashCode());
|
||||||
|
StyleDefinitionsPart $styleDefinitionsPart = this.getStyleDefinitionsPart();
|
||||||
|
result = result * 59 + ($styleDefinitionsPart == null ? 43 : $styleDefinitionsPart.hashCode());
|
||||||
|
Map<String, NumPr> $numberingDefinitionsMap = this.getNumberingDefinitionsMap();
|
||||||
|
result = result * 59 + ($numberingDefinitionsMap == null ? 43 : ((Object)$numberingDefinitionsMap).hashCode());
|
||||||
|
Map<String, ImageBO> $imageMap = this.getImageMap();
|
||||||
|
result = result * 59 + ($imageMap == null ? 43 : ((Object)$imageMap).hashCode());
|
||||||
|
Boolean $newParseVersion = this.getNewParseVersion();
|
||||||
|
result = result * 59 + ($newParseVersion == null ? 43 : ((Object)$newParseVersion).hashCode());
|
||||||
|
Integer $count = this.getCount();
|
||||||
|
result = result * 59 + ($count == null ? 43 : ((Object)$count).hashCode());
|
||||||
|
Long $fileId = this.getFileId();
|
||||||
|
result = result * 59 + ($fileId == null ? 43 : ((Object)$fileId).hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ParseBO(tempDir=" + this.getTempDir() + ", sourceFilePath=" + this.getSourceFilePath() + ", sourceFileUrl=" + this.getSourceFileUrl() + ", sourceFileMd5=" + this.getSourceFileMd5() + ", sourceFileBakPath=" + this.getSourceFileBakPath() + ", htmlPath=" + this.getHtmlPath() + ", imagesDir=" + this.getImagesDir() + ", ossFolder=" + this.getOssFolder() + ", transformer=" + this.getTransformer() + ", latexReplaceList=" + this.getLatexReplaceList() + ", imgMap=" + this.getImgMap() + ", shapeImgMap=" + this.getShapeImgMap() + ", openOfficeXmlMap=" + this.getOpenOfficeXmlMap() + ", mathTypeMap=" + this.getMathTypeMap() + ", eqLatexMap=" + this.getEqLatexMap() + ", styleDefinitionsPart=" + this.getStyleDefinitionsPart() + ", numberingDefinitionsMap=" + this.getNumberingDefinitionsMap() + ", imageMap=" + this.getImageMap() + ", newParseVersion=" + this.getNewParseVersion() + ", count=" + this.getCount() + ", fileId=" + this.getFileId() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
42
java-src/domain/bo/TagStyleBO.java
Normal file
42
java-src/domain/bo/TagStyleBO.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.domain.bo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class TagStyleBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5541312317983576319L;
|
||||||
|
private String tagName;
|
||||||
|
private String attr;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public String getTagName() {
|
||||||
|
return this.tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttr() {
|
||||||
|
return this.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setTagName(String tagName) {
|
||||||
|
this.tagName = tagName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setAttr(String attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
132
java-src/domain/param/ParseParam.java
Normal file
132
java-src/domain/param/ParseParam.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* com.alibaba.fastjson.annotation.JSONField
|
||||||
|
* org.springframework.web.multipart.MultipartFile
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.domain.param;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
public class ParseParam
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = 4113866861283462539L;
|
||||||
|
private String sourceUrl;
|
||||||
|
@JSONField(serialize=false)
|
||||||
|
private MultipartFile sourceFile;
|
||||||
|
private List<JSONObject> latexReplaceList;
|
||||||
|
private Boolean newParseVersion;
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
public String getSourceUrl() {
|
||||||
|
return this.sourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipartFile getSourceFile() {
|
||||||
|
return this.sourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JSONObject> getLatexReplaceList() {
|
||||||
|
return this.latexReplaceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewParseVersion() {
|
||||||
|
return this.newParseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFileId() {
|
||||||
|
return this.fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseParam setSourceUrl(String sourceUrl) {
|
||||||
|
this.sourceUrl = sourceUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseParam setSourceFile(MultipartFile sourceFile) {
|
||||||
|
this.sourceFile = sourceFile;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseParam setLatexReplaceList(List<JSONObject> latexReplaceList) {
|
||||||
|
this.latexReplaceList = latexReplaceList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseParam setNewParseVersion(Boolean newParseVersion) {
|
||||||
|
this.newParseVersion = newParseVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseParam setFileId(Long fileId) {
|
||||||
|
this.fileId = fileId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ParseParam)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ParseParam other = (ParseParam)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceUrl = this.getSourceUrl();
|
||||||
|
String other$sourceUrl = other.getSourceUrl();
|
||||||
|
if (this$sourceUrl == null ? other$sourceUrl != null : !this$sourceUrl.equals(other$sourceUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
MultipartFile this$sourceFile = this.getSourceFile();
|
||||||
|
MultipartFile other$sourceFile = other.getSourceFile();
|
||||||
|
if (this$sourceFile == null ? other$sourceFile != null : !this$sourceFile.equals(other$sourceFile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<JSONObject> this$latexReplaceList = this.getLatexReplaceList();
|
||||||
|
List<JSONObject> other$latexReplaceList = other.getLatexReplaceList();
|
||||||
|
if (this$latexReplaceList == null ? other$latexReplaceList != null : !((Object)this$latexReplaceList).equals(other$latexReplaceList)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$newParseVersion = this.getNewParseVersion();
|
||||||
|
Boolean other$newParseVersion = other.getNewParseVersion();
|
||||||
|
if (this$newParseVersion == null ? other$newParseVersion != null : !((Object)this$newParseVersion).equals(other$newParseVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Long this$fileId = this.getFileId();
|
||||||
|
Long other$fileId = other.getFileId();
|
||||||
|
return !(this$fileId == null ? other$fileId != null : !((Object)this$fileId).equals(other$fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof ParseParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
String $sourceUrl = this.getSourceUrl();
|
||||||
|
result = result * 59 + ($sourceUrl == null ? 43 : $sourceUrl.hashCode());
|
||||||
|
MultipartFile $sourceFile = this.getSourceFile();
|
||||||
|
result = result * 59 + ($sourceFile == null ? 43 : $sourceFile.hashCode());
|
||||||
|
List<JSONObject> $latexReplaceList = this.getLatexReplaceList();
|
||||||
|
result = result * 59 + ($latexReplaceList == null ? 43 : ((Object)$latexReplaceList).hashCode());
|
||||||
|
Boolean $newParseVersion = this.getNewParseVersion();
|
||||||
|
result = result * 59 + ($newParseVersion == null ? 43 : ((Object)$newParseVersion).hashCode());
|
||||||
|
Long $fileId = this.getFileId();
|
||||||
|
result = result * 59 + ($fileId == null ? 43 : ((Object)$fileId).hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ParseParam(sourceUrl=" + this.getSourceUrl() + ", sourceFile=" + this.getSourceFile() + ", latexReplaceList=" + this.getLatexReplaceList() + ", newParseVersion=" + this.getNewParseVersion() + ", fileId=" + this.getFileId() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
79
java-src/enums/AttrEnum.java
Normal file
79
java-src/enums/AttrEnum.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.enums;
|
||||||
|
|
||||||
|
public enum AttrEnum {
|
||||||
|
STYLE("style"),
|
||||||
|
COLOR("color"),
|
||||||
|
BACKGROUND_COLOR("background-color"),
|
||||||
|
BACKGROUND("background"),
|
||||||
|
FONT_WEIGHT("font-weight"),
|
||||||
|
FONT_SIZE("font-size"),
|
||||||
|
MIN_HEIGHT("min-height"),
|
||||||
|
LINE_HEIGHT("line-height"),
|
||||||
|
TEXT_ALIGN("text-align"),
|
||||||
|
PADDING("padding"),
|
||||||
|
PADDING_TOP("padding-top"),
|
||||||
|
PADDING_RIGHT("padding-right"),
|
||||||
|
PADDING_BOTTOM("padding-bottom"),
|
||||||
|
PADDING_LEFT("padding-left"),
|
||||||
|
TEXT_INDENT("text-indent"),
|
||||||
|
MARGIN("margin"),
|
||||||
|
MARGIN_TOP("margin-top"),
|
||||||
|
MARGIN_RIGHT("margin-right"),
|
||||||
|
MARGIN_BOTTOM("margin-bottom"),
|
||||||
|
MARGIN_LEFT("margin-left"),
|
||||||
|
VERTICAL_ALIGN("vertical-align"),
|
||||||
|
FONT_FAMILY("font-family"),
|
||||||
|
FONT_STYLE("font-style"),
|
||||||
|
FACE("face"),
|
||||||
|
SIZE("size"),
|
||||||
|
HREF("href"),
|
||||||
|
SRC("src"),
|
||||||
|
WIDTH("width"),
|
||||||
|
HEIGHT("height"),
|
||||||
|
CLASS("class"),
|
||||||
|
BORDER_STYLE("border-style"),
|
||||||
|
COLSPAN("colspan"),
|
||||||
|
ROWSPAN("rowspan"),
|
||||||
|
TEXT_DECORATION("text-decoration"),
|
||||||
|
BACKGROUND_IMAGE("background-image"),
|
||||||
|
TINYMCE_HORIZONTAL_LINE("tinymce-horizontal-line"),
|
||||||
|
TINYMCE_LINE_NUMBER("tinymce-line-number"),
|
||||||
|
TEXT_EMPHASIS_POSITION("text-emphasis-position"),
|
||||||
|
DATA_TYPE("data-type"),
|
||||||
|
SEGMENT("segment"),
|
||||||
|
BUSINESS("business"),
|
||||||
|
LIST_STYLE_TYPE("list-style-type"),
|
||||||
|
ALT("alt"),
|
||||||
|
NAME("name"),
|
||||||
|
DATA_HREF("data-href"),
|
||||||
|
DATA_File_SUFFIX("data-file-suffix"),
|
||||||
|
TINYMCE_LINK("tinymce-link"),
|
||||||
|
DATA_RESOURCE_URL("data-resource-url"),
|
||||||
|
DATA_COURSE_NETWORK_LESSON_RESOURCE_NAME("data-course-network-lesson-resource-name"),
|
||||||
|
TINYMCE_HOLLOWOUT("tinymce-hollowOut"),
|
||||||
|
TINYMCE_HIDDENONLY("tinymce-hiddenOnly"),
|
||||||
|
TINYMCE_HIDDENANDANSWER("tinymce-hiddenAndAnswer"),
|
||||||
|
BORDER("border"),
|
||||||
|
BORDER_LEFT("border-left"),
|
||||||
|
BORDER_BOTTOM("border-bottom"),
|
||||||
|
BORDER_RIGHT("border-right"),
|
||||||
|
BORDER_TOP("border-top"),
|
||||||
|
BORDER_LEFT_COLOR("border-left-color"),
|
||||||
|
BORDER_BOTTOM_COLOR("border-bottom-color"),
|
||||||
|
BORDER_RIGHT_COLOR("border-right-color"),
|
||||||
|
BORDER_TOP_COLOR("border-top-color");
|
||||||
|
|
||||||
|
private final String attr;
|
||||||
|
|
||||||
|
public String getAttr() {
|
||||||
|
return this.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttrEnum(String attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
140
java-src/enums/TagEnum.java
Normal file
140
java-src/enums/TagEnum.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jby.common.util.DistinctUtils
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.jsoup.nodes.Element
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.enums;
|
||||||
|
|
||||||
|
import com.jby.common.util.DistinctUtils;
|
||||||
|
import com.jby.officecore.domain.bo.TagStyleBO;
|
||||||
|
import com.jby.officecore.enums.AttrEnum;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
public enum TagEnum {
|
||||||
|
H1("h1", true, null),
|
||||||
|
H2("h2", true, null),
|
||||||
|
H3("h3", true, null),
|
||||||
|
H4("h4", true, null),
|
||||||
|
P("p", false, null),
|
||||||
|
DIV("div", false, null),
|
||||||
|
FONT("font", false, Stream.of(AttrEnum.FACE, AttrEnum.SIZE, AttrEnum.COLOR).collect(Collectors.toList())),
|
||||||
|
STRIKE("strike", true, null),
|
||||||
|
BR("br", false, null),
|
||||||
|
PBR("pbr", false, null),
|
||||||
|
HR("hr", false, null),
|
||||||
|
U("u", true, null),
|
||||||
|
I("i", true, null),
|
||||||
|
SUB("sub", true, null),
|
||||||
|
SUP("sup", true, null),
|
||||||
|
WAVE("wave", true, null),
|
||||||
|
DOT("dot", true, null),
|
||||||
|
B("b", true, null),
|
||||||
|
A("a", false, Stream.of(AttrEnum.HREF).collect(Collectors.toList())),
|
||||||
|
TABLE("table", false, null),
|
||||||
|
UL("ul", false, null),
|
||||||
|
OL("ol", false, null),
|
||||||
|
LI("li", false, null),
|
||||||
|
IMG("img", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
IMQ("imq", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
VIDEO("video", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
SOURCE("source", false, null),
|
||||||
|
MATH("math", false, null),
|
||||||
|
TAB("tab", false, null),
|
||||||
|
TEXT("#text", false, null),
|
||||||
|
SPAN("span", false, Stream.of(AttrEnum.CLASS).collect(Collectors.toList())),
|
||||||
|
EM("em", true, null),
|
||||||
|
STRONG("strong", true, null);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Boolean dealTagStyle;
|
||||||
|
private final List<AttrEnum> attrList;
|
||||||
|
public static final Set<String> H_P_SET;
|
||||||
|
public static final Set<String> H_SET;
|
||||||
|
public static final Set<String> UL_OL_SET;
|
||||||
|
private static final Map<String, TagEnum> MAP;
|
||||||
|
|
||||||
|
public static TagEnum getTagEnum(String tagName) {
|
||||||
|
return MAP.get(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<TagStyleBO> getStyleList(Element element) {
|
||||||
|
ArrayList<TagStyleBO> styleList = new ArrayList<TagStyleBO>();
|
||||||
|
TagEnum tagEnum = TagEnum.getTagEnum(element.tagName());
|
||||||
|
if (Objects.nonNull((Object)tagEnum) && Boolean.TRUE.equals(tagEnum.dealTagStyle)) {
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(tagEnum.getName()).setValue(tagEnum.getName()));
|
||||||
|
}
|
||||||
|
if (Objects.nonNull((Object)tagEnum) && CollectionUtils.isNotEmpty(tagEnum.getAttrList())) {
|
||||||
|
for (AttrEnum attrEnum : tagEnum.getAttrList()) {
|
||||||
|
if (!StringUtils.isNotBlank((CharSequence)element.attr(attrEnum.getAttr())) || "null".equals(element.attr(attrEnum.getAttr()))) continue;
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(attrEnum.getAttr()).setValue(element.attr(attrEnum.getAttr())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> styles = StringUtils.isBlank((CharSequence)element.attr(AttrEnum.STYLE.getAttr())) ? Collections.emptyList() : Arrays.asList(element.attr(AttrEnum.STYLE.getAttr()).split(";"));
|
||||||
|
styles.forEach(style -> {
|
||||||
|
if (StringUtils.isBlank((CharSequence)style)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] split = style.split(":");
|
||||||
|
if (split.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String attr = split[0].trim();
|
||||||
|
String value = split[1].trim();
|
||||||
|
if (StringUtils.isBlank((CharSequence)value) || "null".equals(value) || StringUtils.isBlank((CharSequence)attr) || "null".equals(attr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(attr).setValue(value));
|
||||||
|
});
|
||||||
|
return styleList.stream().filter(DistinctUtils.distinctByKey(TagStyleBO::getAttr)).filter(e -> !e.getValue().contains("auto")).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getStyleMap(Element element) {
|
||||||
|
List<TagStyleBO> styleList = TagEnum.getStyleList(element);
|
||||||
|
return styleList.stream().collect(Collectors.toMap(TagStyleBO::getAttr, TagStyleBO::getValue, (l, r) -> l));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getDealTagStyle() {
|
||||||
|
return this.dealTagStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AttrEnum> getAttrList() {
|
||||||
|
return this.attrList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TagEnum(String name, Boolean dealTagStyle, List<AttrEnum> attrList) {
|
||||||
|
this.name = name;
|
||||||
|
this.dealTagStyle = dealTagStyle;
|
||||||
|
this.attrList = attrList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
H_P_SET = Stream.of(H1.getName(), H2.getName(), H3.getName(), H4.getName(), P.getName()).collect(Collectors.toSet());
|
||||||
|
H_SET = Stream.of(H1.getName(), H2.getName(), H3.getName(), H4.getName()).collect(Collectors.toSet());
|
||||||
|
UL_OL_SET = Stream.of(UL.getName(), OL.getName()).collect(Collectors.toSet());
|
||||||
|
MAP = new HashMap<String, TagEnum>();
|
||||||
|
for (TagEnum tagEnum : TagEnum.values()) {
|
||||||
|
MAP.put(tagEnum.getName(), tagEnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
110
java-src/pool/OMml2MmlTransformerPool.java
Normal file
110
java-src/pool/OMml2MmlTransformerPool.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
2255
java-src/service/impl/FileServiceImpl.java
Normal file
2255
java-src/service/impl/FileServiceImpl.java
Normal file
File diff suppressed because it is too large
Load Diff
112
java-src/utils/DigestUtil.java
Normal file
112
java-src/utils/DigestUtil.java
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.jby.common.util.FileUtil
|
||||||
|
* org.apache.commons.codec.binary.Hex
|
||||||
|
* org.apache.commons.codec.digest.DigestUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
* org.springframework.stereotype.Component
|
||||||
|
* org.springframework.web.multipart.MultipartFile
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils;
|
||||||
|
|
||||||
|
import com.jby.common.util.FileUtil;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import org.apache.commons.codec.binary.Hex;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DigestUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DigestUtil.class);
|
||||||
|
private static final String DIGEST_MD5 = "MD5";
|
||||||
|
private static final String DIGEST_SHA1 = "SHA-1";
|
||||||
|
|
||||||
|
public static String getMd5(String str) {
|
||||||
|
if (StringUtils.isBlank((CharSequence)str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return DigestUtils.md5Hex((String)str);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u83b7\u53d6\u5b57\u7b26\u4e32MD5\u503c\u5f02\u5e38:{}", (Object)e.getMessage(), (Object)e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileMd5(String filePath) {
|
||||||
|
File file = FileUtil.getFile((String)filePath);
|
||||||
|
return DigestUtil.getFileMd5(FileUtil.getMultipartFile((String)file.getName(), (File)file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileMd5(MultipartFile file) {
|
||||||
|
return DigestUtil.getDigest(file, DIGEST_MD5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getSha1(String str) {
|
||||||
|
if (StringUtils.isBlank((CharSequence)str)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return DigestUtils.sha1Hex((String)str);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u83b7\u53d6\u5b57\u7b26\u4e32sha1\u503c\u5f02\u5e38:{}", (Object)e.getMessage(), (Object)e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileSha1(String filePath) {
|
||||||
|
File file = FileUtil.getFile((String)filePath);
|
||||||
|
return DigestUtil.getFileSha1(FileUtil.getMultipartFile((String)file.getName(), (File)file));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFileSha1(MultipartFile file) {
|
||||||
|
return DigestUtil.getDigest(file, DIGEST_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING - Removed try catching itself - possible behaviour change.
|
||||||
|
*/
|
||||||
|
private static String getDigest(MultipartFile file, String digest) {
|
||||||
|
InputStream is = null;
|
||||||
|
try {
|
||||||
|
int length;
|
||||||
|
MessageDigest messageDigest = MessageDigest.getInstance(digest);
|
||||||
|
is = file.getInputStream();
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
while ((length = is.read(buffer)) != -1) {
|
||||||
|
messageDigest.update(buffer, 0, length);
|
||||||
|
}
|
||||||
|
String string = new String(Hex.encodeHex((byte[])messageDigest.digest()));
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u83b7\u53d6\u6458\u8981\u503c\u5f02\u5e38:{}", (Object)e.getMessage(), (Object)e);
|
||||||
|
String string = null;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
if (is != null) {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1034
java-src/utils/EqToLatexUtil.java
Normal file
1034
java-src/utils/EqToLatexUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
501
java-src/utils/LatexUtil.java
Normal file
501
java-src/utils/LatexUtil.java
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* cn.hutool.core.thread.ThreadUtil
|
||||||
|
* com.alibaba.fastjson.JSON
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* com.alibaba.fastjson.TypeReference
|
||||||
|
* com.alibaba.fastjson.parser.Feature
|
||||||
|
* com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
* com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
* com.jby.common.util.FileUtil
|
||||||
|
* com.jby.common.util.HttpClientUtil
|
||||||
|
* com.jby.common.util.UniqueUtil
|
||||||
|
* fmath.conversion.ConvertFromLatexToMathML
|
||||||
|
* fmath.conversion.ConvertFromMathMLToLatex
|
||||||
|
* org.apache.commons.collections4.MapUtils
|
||||||
|
* org.apache.commons.exec.CommandLine
|
||||||
|
* org.apache.commons.exec.DefaultExecutor
|
||||||
|
* org.apache.commons.exec.ExecuteStreamHandler
|
||||||
|
* org.apache.commons.exec.PumpStreamHandler
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.scilab.forge.jlatexmath.TeXFormula
|
||||||
|
* org.scilab.forge.jlatexmath.TeXIcon
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
* org.springframework.beans.factory.annotation.Autowired
|
||||||
|
* org.springframework.stereotype.Component
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.alibaba.fastjson.TypeReference;
|
||||||
|
import com.alibaba.fastjson.parser.Feature;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import com.jby.common.util.FileUtil;
|
||||||
|
import com.jby.common.util.HttpClientUtil;
|
||||||
|
import com.jby.common.util.UniqueUtil;
|
||||||
|
import com.jby.officecore.config.OfficeCoreConfig;
|
||||||
|
import com.jby.officecore.domain.bo.LatexInfoBO;
|
||||||
|
import com.jby.officecore.utils.MapUtil;
|
||||||
|
import fmath.conversion.ConvertFromLatexToMathML;
|
||||||
|
import fmath.conversion.ConvertFromMathMLToLatex;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Insets;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import org.apache.commons.collections4.MapUtils;
|
||||||
|
import org.apache.commons.exec.CommandLine;
|
||||||
|
import org.apache.commons.exec.DefaultExecutor;
|
||||||
|
import org.apache.commons.exec.ExecuteStreamHandler;
|
||||||
|
import org.apache.commons.exec.PumpStreamHandler;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.scilab.forge.jlatexmath.TeXFormula;
|
||||||
|
import org.scilab.forge.jlatexmath.TeXIcon;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class LatexUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LatexUtil.class);
|
||||||
|
private static final String LATEX_PATTERN_STR = "\\$\\$(.*?)\\$\\$|\\\\\\((.*?)\\\\\\)";
|
||||||
|
private static final Pattern LATEX_PATTERN = Pattern.compile("\\$\\$(.*?)\\$\\$|\\\\\\((.*?)\\\\\\)");
|
||||||
|
private static final String LATEX_SELF_PATTERN_STR = "(?<=\\$\\$)(.*?)(?=\\$\\$)|(?<=\\\\\\()(.*?)(?=\\\\\\))";
|
||||||
|
private static final Pattern LATEX_SELF_PATTERN = Pattern.compile("(?<=\\$\\$)(.*?)(?=\\$\\$)|(?<=\\\\\\()(.*?)(?=\\\\\\))");
|
||||||
|
private static final Pattern CHINESE_PATTERN = Pattern.compile("[\u4e00-\u9fa5]");
|
||||||
|
private static final String LATEX_REPLACE = "@@@";
|
||||||
|
public static final String LATEX_SPLIT = "###";
|
||||||
|
private static OfficeCoreConfig officeCoreConfig;
|
||||||
|
private static final ObjectMapper MAPPER;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setOfficeCoreConfig(OfficeCoreConfig officeCoreConfig) {
|
||||||
|
LatexUtil.officeCoreConfig = officeCoreConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String dealLatexContent(String content, Map<String, LatexInfoBO> latexMap, Boolean newTransferVersion) {
|
||||||
|
String result = content.replaceAll(LATEX_PATTERN_STR, LATEX_REPLACE);
|
||||||
|
Matcher mather = LatexUtil.getLatexMatcher(content);
|
||||||
|
while (mather.find()) {
|
||||||
|
String latex = mather.group();
|
||||||
|
String latexKey = "";
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)latex)) {
|
||||||
|
String cleanLatex;
|
||||||
|
Matcher matcher;
|
||||||
|
latex = latex.replaceAll(" ", " ");
|
||||||
|
latex = latex.replaceAll(">", ">");
|
||||||
|
latex = latex.replaceAll("<", "<");
|
||||||
|
latex = latex.replace("<br/>", "").replace("\\ ", "");
|
||||||
|
if (Boolean.TRUE.equals(newTransferVersion)) {
|
||||||
|
latexKey = latex.replaceAll(LATEX_SELF_PATTERN_STR, UniqueUtil.getUuid());
|
||||||
|
matcher = LATEX_SELF_PATTERN.matcher(latex);
|
||||||
|
if (matcher.find() && StringUtils.isNotBlank((CharSequence)(cleanLatex = matcher.group()))) {
|
||||||
|
latex = cleanLatex;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
latex = LatexUtil.formatLatex(latex);
|
||||||
|
latexKey = latex.replaceAll(LATEX_SELF_PATTERN_STR, UniqueUtil.getUuid());
|
||||||
|
matcher = LATEX_SELF_PATTERN.matcher(latex);
|
||||||
|
if (matcher.find() && StringUtils.isNotBlank((CharSequence)(cleanLatex = matcher.group()))) {
|
||||||
|
latex = cleanLatex;
|
||||||
|
}
|
||||||
|
latex = LatexUtil.wrapLatexExpressionWithBraces(latex);
|
||||||
|
}
|
||||||
|
latexMap.put(latexKey, new LatexInfoBO().setLatexKey(latexKey).setLatex(latex));
|
||||||
|
}
|
||||||
|
result = StringUtils.replaceOnce((String)result, (String)LATEX_REPLACE, (String)(" ###" + latexKey + LATEX_SPLIT + " "));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String wrapLatexExpressionWithBraces(String latex) {
|
||||||
|
String[] simpleOperators;
|
||||||
|
String[] limitsOperators;
|
||||||
|
if (StringUtils.isBlank((CharSequence)latex)) {
|
||||||
|
return latex;
|
||||||
|
}
|
||||||
|
latex = latex.replaceAll("\\\\xleftrightarrow\\[([^\\]]+)\\]\\{([^}]+)\\}", "\\\\underset{$1}{\\\\overset{$2}{\\\\longleftrightarrow}}");
|
||||||
|
latex = latex.replaceAll("\\\\xrightleftharpoons\\[([^\\]]+)\\]\\{([^}]+)\\}", "\\\\underset{$1}{\\\\overset{$2}{\\\\mathop{\\\\rightleftharpoons }}}\\\\,");
|
||||||
|
latex = latex.replaceAll("\\\\enclose\\{madruwb\\}\\{([^}]+)\\}", "\\\\left. \\{\\\\underline \\{$1\\}\\} \\\\right|");
|
||||||
|
latex = latex.replaceAll("\\\\enclose\\{actuarial\\}\\{([^}]+)\\}", "\\\\left. \\{\\\\overline \\{$1\\}\\} \\\\right|");
|
||||||
|
String[] operators = new String[]{"prod", "coprod", "sum", "int", "bigcap", "bigcup", "iint", "iiint", "oint"};
|
||||||
|
for (String op : limitsOperators = new String[]{"prod", "coprod", "sum", "bigcap", "bigcup", "iint", "iiint", "oint"}) {
|
||||||
|
latex = latex.replaceAll("(\\\\displaystyle\\\\" + op + "\\\\limits(?:\\s*_\\{[^}]*\\})?(?:\\s*\\^\\{[^}]*\\}|\\s*_\\{[^}]*\\})\\s*)([a-zA-Z0-9]+)(?![a-zA-Z0-9])", "$1{$2}");
|
||||||
|
}
|
||||||
|
for (String op : operators) {
|
||||||
|
latex = latex.replaceAll("(\\\\displaystyle\\\\" + op + "(?:\\s*_\\{[^}]*\\})?(?:\\s*\\^\\{[^}]*\\}|\\s*_\\{[^}]*\\})\\s*)([a-zA-Z0-9]+)(?![a-zA-Z0-9])", "$1{$2}");
|
||||||
|
}
|
||||||
|
for (String op : simpleOperators = new String[]{"iint", "iiint", "oint"}) {
|
||||||
|
latex = latex.replaceAll("(\\\\" + op + "\\s*_\\{[^}]*\\}\\s*)([a-zA-Z0-9]+)(?![a-zA-Z0-9])", "$1{$2}");
|
||||||
|
}
|
||||||
|
return latex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatLatex(String latex) {
|
||||||
|
if (StringUtils.isBlank((CharSequence)latex)) {
|
||||||
|
return latex;
|
||||||
|
}
|
||||||
|
StringBuilder latexBuilder = new StringBuilder();
|
||||||
|
try {
|
||||||
|
latex = latex.replace("leqslant", "leq");
|
||||||
|
latex = latex.replace("geqslant", "geq");
|
||||||
|
boolean inChinese = false;
|
||||||
|
char[] cArray = latex.toCharArray();
|
||||||
|
int n = cArray.length;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
Character c = Character.valueOf(cArray[i]);
|
||||||
|
Matcher chineseMatch = CHINESE_PATTERN.matcher(c.toString());
|
||||||
|
if (chineseMatch.find()) {
|
||||||
|
if (inChinese) {
|
||||||
|
latexBuilder.append(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
latexBuilder.append("\\mbox{").append(c);
|
||||||
|
inChinese = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inChinese) {
|
||||||
|
inChinese = false;
|
||||||
|
latexBuilder.append("}");
|
||||||
|
}
|
||||||
|
latexBuilder.append(c);
|
||||||
|
}
|
||||||
|
if (inChinese) {
|
||||||
|
latexBuilder.append("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("formatLatex error, latex:{}, result:{}", new Object[]{latex, latexBuilder.toString(), e});
|
||||||
|
}
|
||||||
|
return latexBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void latexToImg(String latex, String path) {
|
||||||
|
try {
|
||||||
|
TeXFormula formula = new TeXFormula(latex);
|
||||||
|
TeXIcon icon = formula.createTeXIcon(0, 40.0f);
|
||||||
|
icon.setInsets(new Insets(5, 5, 5, 5));
|
||||||
|
BufferedImage bImg = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), 6);
|
||||||
|
Graphics2D g2d = bImg.createGraphics();
|
||||||
|
g2d.setColor(Color.white);
|
||||||
|
g2d.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight());
|
||||||
|
JLabel jl = new JLabel();
|
||||||
|
jl.setForeground(new Color(0, 0, 0));
|
||||||
|
icon.paintIcon((java.awt.Component)jl, (Graphics)g2d, 0, 0);
|
||||||
|
File out = FileUtil.getFile((String)path);
|
||||||
|
ImageIO.write((RenderedImage)bImg, "png", out);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("latexToImg error\uff1alatex={}, path={}, ", new Object[]{latex, path, e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void latexToMathML(Map<String, LatexInfoBO> latexMap) {
|
||||||
|
Collection<Map<String, LatexInfoBO>> maps = MapUtil.splitMap(latexMap, 500);
|
||||||
|
HashMap convertMap = new HashMap();
|
||||||
|
maps.forEach(map -> {
|
||||||
|
if (MapUtils.isEmpty((Map)map)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap convertParam = new HashMap();
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
HashMap param = new HashMap();
|
||||||
|
map.values().forEach(info -> param.put(info.getLatexKey(), info.getLatex()));
|
||||||
|
convertParam.put("latex", param);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
result = HttpClientUtil.doPost((String)officeCoreConfig.getForeLatexToMathMlApi(), (String)JSON.toJSONString(convertParam));
|
||||||
|
log.info("latex\u8f6cmathML\u8c03\u7528python\u63a5\u53e3\u6210\u529f, \u8017\u65f6\uff1a{}\u6beb\u79d2", (Object)(System.currentTimeMillis() - ts));
|
||||||
|
Map convertResult = (Map)HttpClientUtil.parseResult((String)result, (TypeReference)new TypeReference<Map<String, String>>(){});
|
||||||
|
if (MapUtils.isNotEmpty((Map)convertResult)) {
|
||||||
|
convertMap.putAll(convertResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("latex\u8f6cmathML\u8c03\u7528python\u63a5\u53e3\u5f02\u5e38, param:{}, result:{}", new Object[]{JSON.toJSONString(convertParam), result, e});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
latexMap.values().forEach(info -> {
|
||||||
|
String mathMl = (String)convertMap.get(info.getLatexKey());
|
||||||
|
if (StringUtils.isBlank((CharSequence)mathMl)) {
|
||||||
|
try {
|
||||||
|
mathMl = LatexUtil.convertFromLatexToMathML(info.getLatex());
|
||||||
|
log.info("latex\u8f6cmathML\u672c\u5730\u8f6c\u6362,latex:{}", (Object)info.getLatex());
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("latex\u8f6cmathML\u672c\u5730\u8f6c\u6362\u5f02\u5e38, info:{}", (Object)JSON.toJSONString((Object)info), (Object)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)mathMl)) {
|
||||||
|
mathMl = mathMl.replaceAll("(?i)⟂", "⊥");
|
||||||
|
}
|
||||||
|
info.setMathMl(mathMl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertFromLatexToMathML(String latex) {
|
||||||
|
String mathMl = ConvertFromLatexToMathML.convertToMathML((String)latex);
|
||||||
|
mathMl = mathMl.replace("\n", "").replaceFirst("<math", "<math xmlns=\"http://www.w3.org/1998/Math/MathML\" ").replace("∗", "\u2217").replace("∴", "\u2234").replace("∝", "\u221d").replace("≈", "\u2248").replace("≠", "\u2260").replace("∋", "\u220b").replace("∈", "\u2208").replace("∉", "\u2209").replace("α", "\u03b1").replace("β", "\u03b2").replace("γ", "\u03b3").replace("δ", "\u03b4").replace("ϵ", "\u03b5").replace("ε", "\u03f5").replace("ζ", "\u03b6").replace("η", "\u03b7").replace("θ", "\u03b8").replace("ϑ", "\u03d1").replace("ι", "\u03b9").replace("κ", "\u03ba").replace("λ", "\u03bb").replace("μ", "\u03bc").replace("ν", "\u03bd").replace("ρ", "\u03c1").replace("σ", "\u03c3").replace("φ", "\u03d5").replace("ϕ", "\u03c6").replace("ω", "\u03c9").replace("Λ", "\u039b").replace("Σ", "\u03a3").replace("Ω", "\u03a9").replace("⊂", "\u2282").replace("⊃", "\u2283").replace("⊆", "\u2286").replace("⊇", "\u2287").replace("∇", "\u2207").replace("…", "\u2026").replace("↓", "\u2193").replace("⇓", "\u21d3").replace("↑", "\u2191").replace("⇑", "\u21d1").replace("→", "\u2192").replace("⇒", "\u21d2").replace("←", "\u2190").replace("⇐", "\u21d0").replace("↔", "\u2194").replace("⇔", "\u21d4").replace("ℜ", "\u211c").replace("ℑ", "\u2111").replace("∂", "\u2202").replace("∞", "\u221e").replace("ℵ", "\u2135").replace("∃", "\u2203").replace("∅", "\u2205").replace("∪", "\u222a").replace("∩", "\u2229").replace("°", "\u00b0").replace("∠", "\u2220").replace("∫", "\u222b").replace("∏", "\u220f").replace("∐", "\u2210");
|
||||||
|
return mathMl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mathMlToLatex(Map<String, String> formulaMap, Path path) {
|
||||||
|
if (!MapUtils.isEmpty(formulaMap)) {
|
||||||
|
log.info("mathML\u8f6clatex\u6570\u91cf:{}", (Object)formulaMap.size());
|
||||||
|
}
|
||||||
|
Collection<Map<String, String>> maps = MapUtil.splitMap(formulaMap, 500);
|
||||||
|
HashMap convertMap = new HashMap();
|
||||||
|
maps.forEach(map -> {
|
||||||
|
block11: {
|
||||||
|
if (MapUtils.isEmpty((Map)map)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HashMap<String, Map> convertParam = new HashMap<String, Map>();
|
||||||
|
String result = "";
|
||||||
|
if (Boolean.TRUE.equals(officeCoreConfig.getMathMlToLatexApiSwitch())) {
|
||||||
|
try {
|
||||||
|
convertParam.put("mathml_dict", (Map)map);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
result = HttpClientUtil.doPost((String)officeCoreConfig.getMathMlToLatexApi(), convertParam, (int)5000, (int)15000);
|
||||||
|
log.info("mathML\u8f6clatex\u8c03\u7528python\u63a5\u53e3\u6210\u529f, \u8017\u65f6\uff1a{}\u6beb\u79d2", (Object)(System.currentTimeMillis() - ts));
|
||||||
|
result = result.replace("<", "\\\\lt ");
|
||||||
|
Map convertResult = (Map)HttpClientUtil.parseResult((String)result, (TypeReference)new TypeReference<Map<String, String>>(){});
|
||||||
|
if (MapUtils.isNotEmpty((Map)convertResult)) {
|
||||||
|
convertMap.putAll(convertResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mathML\u8f6clatex\u8c03\u7528python\u63a5\u53e3\u5f02\u5e38, param:{}, result:{}", new Object[]{JSON.toJSONString(convertParam), result, e});
|
||||||
|
}
|
||||||
|
ThreadUtil.sleep((long)2000L);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Map convertResult;
|
||||||
|
JSONObject json;
|
||||||
|
String data;
|
||||||
|
convertParam.put("mathml_dict", (Map)map);
|
||||||
|
File tempFile = File.createTempFile("mathml_input-", ".json", new File(path.toString()));
|
||||||
|
String absolutePath = tempFile.getAbsolutePath();
|
||||||
|
if (!StringUtils.isNotBlank((CharSequence)absolutePath) || map.isEmpty()) break block11;
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
mapper.writeValue(tempFile, convertParam);
|
||||||
|
CommandLine cmdLine = CommandLine.parse((String)officeCoreConfig.getPythonCommand());
|
||||||
|
cmdLine.addArgument(officeCoreConfig.getTransformScriptPath() + "/mathml2latex/batch_converter.py");
|
||||||
|
cmdLine.addArgument("--input-file=" + absolutePath);
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler((OutputStream)output, (OutputStream)output);
|
||||||
|
DefaultExecutor executor = new DefaultExecutor();
|
||||||
|
executor.setStreamHandler((ExecuteStreamHandler)streamHandler);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
executor.execute(cmdLine);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mathml2latex/batch_converter.py error, \n\u8f93\u51fa:{}", (Object)output, (Object)e);
|
||||||
|
}
|
||||||
|
log.info("mathml2latex/batch_converter.py end, \u6570\u91cf:{}, \u8017\u65f6:{}\u6beb\u79d2", (Object)map.size(), (Object)(System.currentTimeMillis() - ts));
|
||||||
|
String fullOutput = output.toString();
|
||||||
|
CharSequence[] lines = fullOutput.split("\n");
|
||||||
|
result = String.join((CharSequence)"", lines);
|
||||||
|
result = result.replace("<", "\\\\lt ");
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)result) && Objects.nonNull(data = (json = JSONObject.parseObject((String)result)).getString("data")) && MapUtils.isNotEmpty((Map)(convertResult = (Map)JSON.parseObject((String)data, (TypeReference)new TypeReference<Map<String, String>>(){}, (Feature[])new Feature[0])))) {
|
||||||
|
convertMap.putAll(convertResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mathML\u8f6clatex\u8c03\u7528\u672c\u5730python\u7a0b\u5e8f\u5f02\u5e38,param:{}, result:{}", new Object[]{JSON.toJSONString(convertParam), result, e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formulaMap.forEach((formulaKey, mathMl) -> {
|
||||||
|
String latex = (String)convertMap.get(formulaKey);
|
||||||
|
if (StringUtils.isBlank((CharSequence)latex)) {
|
||||||
|
try {
|
||||||
|
latex = ConvertFromMathMLToLatex.convertToLatex((String)mathMl);
|
||||||
|
log.info("mathML\u8f6clatex\u672c\u5730\u8f6c\u6362,latex:{}", (Object)latex);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mathML\u8f6clatex\u672c\u5730\u8f6c\u6362\u5f02\u5e38, mathMl:{}, latex:{}", new Object[]{mathMl, latex, e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)latex) && latex.length() > 2) {
|
||||||
|
formulaMap.put((String)formulaKey, latex.substring(1, latex.length() - 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher getLatexMatcher(String content) {
|
||||||
|
return LATEX_PATTERN.matcher(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mathMlToLatexIgnoreNull(Map<String, String> formulaMap, String copyrightAddresses, Path path) {
|
||||||
|
Map<String, String> map = formulaMap.entrySet().stream().filter(f -> Objects.nonNull(f.getValue())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
LatexUtil.mathMlToLatex(map, path);
|
||||||
|
String[] original = copyrightAddresses.split(",");
|
||||||
|
String[] transformed = (String[])Arrays.stream(original).map(s -> s.chars().mapToObj(c -> {
|
||||||
|
if (c == 46) {
|
||||||
|
return ".";
|
||||||
|
}
|
||||||
|
return "\\mathrm{" + (char)c + "}";
|
||||||
|
}).collect(Collectors.joining())).toArray(String[]::new);
|
||||||
|
String[] splits = (String[])Stream.concat(Arrays.stream(original), Arrays.stream(transformed)).toArray(String[]::new);
|
||||||
|
map.replaceAll((key, value) -> {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Arrays.stream(splits).anyMatch(value::contains) ? null : value;
|
||||||
|
});
|
||||||
|
formulaMap.putAll(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ommlAndMathMlToLatex(Map<String, String> formulaMap, Path path) {
|
||||||
|
if (!MapUtils.isEmpty(formulaMap)) {
|
||||||
|
log.info("Omml\u6216MathML\u8f6clatex\u6570\u91cf:{}", (Object)formulaMap.size());
|
||||||
|
}
|
||||||
|
Collection<Map<String, String>> maps = MapUtil.splitMap(formulaMap, 1000);
|
||||||
|
maps.forEach(map -> {
|
||||||
|
block6: {
|
||||||
|
if (MapUtils.isEmpty((Map)map)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
Map convertResult;
|
||||||
|
File tempFile = File.createTempFile("input-", ".json", new File(path.toString()));
|
||||||
|
String absolutePath = tempFile.getAbsolutePath();
|
||||||
|
if (!StringUtils.isNotBlank((CharSequence)absolutePath) || map.isEmpty()) break block6;
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
mapper.writeValue(tempFile, map);
|
||||||
|
CommandLine cmdLine = CommandLine.parse((String)officeCoreConfig.getPythonCommand());
|
||||||
|
cmdLine.addArgument(officeCoreConfig.getTransformScriptPath() + "/omml_mathml2latex/omml_mathml2latex.py");
|
||||||
|
cmdLine.addArgument("--json");
|
||||||
|
cmdLine.addArgument(absolutePath);
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler((OutputStream)output, (OutputStream)output);
|
||||||
|
DefaultExecutor executor = new DefaultExecutor();
|
||||||
|
executor.setStreamHandler((ExecuteStreamHandler)streamHandler);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
executor.execute(cmdLine);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("/omml_mathml2latex/omml_mathml2latex.py error, \n\u8f93\u51fa:{}", (Object)output, (Object)e);
|
||||||
|
}
|
||||||
|
log.info("/omml_mathml2latex/omml_mathml2latex.py end, \u6570\u91cf:{}, \u8017\u65f6:{}\u6beb\u79d2", (Object)map.size(), (Object)(System.currentTimeMillis() - ts));
|
||||||
|
String fullOutput = output.toString();
|
||||||
|
CharSequence[] lines = fullOutput.split("\n");
|
||||||
|
result = String.join((CharSequence)"", lines);
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)result) && MapUtils.isNotEmpty((Map)(convertResult = (Map)JSON.parseObject((String)result, (TypeReference)new TypeReference<Map<String, String>>(){}, (Feature[])new Feature[0])))) {
|
||||||
|
formulaMap.putAll(convertResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("Omml\u6216MathML\u8f6clatex\u8c03\u7528\u672c\u5730python\u7a0b\u5e8f\u5f02\u5e38,param:{}, result:{}", new Object[]{JSON.toJSONString((Object)map), result, e});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ommlMathMlToLatexIgnoreNull(Map<String, String> formulaMap, String copyrightAddresses, Path path) {
|
||||||
|
Map<String, String> map = formulaMap.entrySet().stream().filter(f -> Objects.nonNull(f.getValue())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
|
LatexUtil.ommlAndMathMlToLatex(map, path);
|
||||||
|
String[] original = copyrightAddresses.split(",");
|
||||||
|
String[] transformed = (String[])Arrays.stream(original).map(s -> s.chars().mapToObj(c -> {
|
||||||
|
if (c == 46) {
|
||||||
|
return ".";
|
||||||
|
}
|
||||||
|
return "\\mathrm{" + (char)c + "}";
|
||||||
|
}).collect(Collectors.joining())).toArray(String[]::new);
|
||||||
|
String[] splits = (String[])Stream.concat(Arrays.stream(original), Arrays.stream(transformed)).toArray(String[]::new);
|
||||||
|
map.replaceAll((key, value) -> {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Arrays.stream(splits).anyMatch(value.replace(" ", "")::contains) ? null : value;
|
||||||
|
});
|
||||||
|
formulaMap.putAll(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void latexAndMathMlToOmml(Map<String, String> latexMathmlMap) {
|
||||||
|
if (!MapUtils.isEmpty(latexMathmlMap)) {
|
||||||
|
log.info("Latex\u6216MathML\u8f6cOmml\u6570\u91cf:{}", (Object)latexMathmlMap.size());
|
||||||
|
}
|
||||||
|
String tempFolder = officeCoreConfig.getTmpPath() + "/download" + File.separator + UniqueUtil.getUuid() + File.separator;
|
||||||
|
Collection<Map<String, String>> maps = MapUtil.splitMap(latexMathmlMap, 1000);
|
||||||
|
maps.forEach(map -> {
|
||||||
|
block10: {
|
||||||
|
if (MapUtils.isEmpty((Map)map)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String result = "";
|
||||||
|
try {
|
||||||
|
Map convertResult;
|
||||||
|
File tempFile = FileUtil.getFile((String)(tempFolder + UniqueUtil.getUuid() + ".json"));
|
||||||
|
String absolutePath = tempFile.getAbsolutePath();
|
||||||
|
if (!StringUtils.isNotBlank((CharSequence)absolutePath) || map.isEmpty()) break block10;
|
||||||
|
MAPPER.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
MAPPER.writeValue(tempFile, map);
|
||||||
|
CommandLine cmdLine = CommandLine.parse((String)officeCoreConfig.getPythonCommand());
|
||||||
|
cmdLine.addArguments(new String[]{officeCoreConfig.getTransformScriptPath() + "/latex_mathml2omml/latex_mathml2omml.py", "--json", absolutePath}, false);
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler((OutputStream)output, (OutputStream)output);
|
||||||
|
DefaultExecutor executor = new DefaultExecutor();
|
||||||
|
executor.setStreamHandler((ExecuteStreamHandler)streamHandler);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
executor.execute(cmdLine);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("/latex_mathml2omml/latex_mathml2omml.py error, \n\u8f93\u51fa:{}", (Object)output, (Object)e);
|
||||||
|
}
|
||||||
|
log.info("/latex_mathml2omml/latex_mathml2omml.py end, \u6570\u91cf:{}, \u8017\u65f6:{}\u6beb\u79d2", (Object)map.size(), (Object)(System.currentTimeMillis() - ts));
|
||||||
|
String fullOutput = output.toString();
|
||||||
|
CharSequence[] lines = fullOutput.split("\n");
|
||||||
|
result = String.join((CharSequence)"", lines);
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)result) && MapUtils.isNotEmpty((Map)(convertResult = (Map)JSON.parseObject((String)result, (TypeReference)new TypeReference<Map<String, String>>(){}, (Feature[])new Feature[0])))) {
|
||||||
|
latexMathmlMap.putAll(convertResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("Latex\u6216MathML\u8f6cOmml\u8c03\u7528\u672c\u5730python\u7a0b\u5e8f\u5f02\u5e38,param:{}, result:{}", new Object[]{JSON.toJSONString((Object)map), result, e});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Boolean.TRUE.equals(officeCoreConfig.getDeleteTmpFile())) {
|
||||||
|
FileUtil.delFolder((String)tempFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
MAPPER = new ObjectMapper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
131
java-src/utils/MathTypeUtil.java
Normal file
131
java-src/utils/MathTypeUtil.java
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.fastjson.JSON
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* org.apache.commons.collections4.MapUtils
|
||||||
|
* org.apache.commons.exec.CommandLine
|
||||||
|
* org.apache.commons.exec.DefaultExecutor
|
||||||
|
* org.apache.commons.exec.ExecuteStreamHandler
|
||||||
|
* org.apache.commons.exec.PumpStreamHandler
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
* org.springframework.beans.factory.annotation.Autowired
|
||||||
|
* org.springframework.stereotype.Component
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.jby.officecore.config.OfficeCoreConfig;
|
||||||
|
import com.jby.officecore.utils.MapUtil;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.OpenOption;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.FileAttribute;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.apache.commons.collections4.MapUtils;
|
||||||
|
import org.apache.commons.exec.CommandLine;
|
||||||
|
import org.apache.commons.exec.DefaultExecutor;
|
||||||
|
import org.apache.commons.exec.ExecuteStreamHandler;
|
||||||
|
import org.apache.commons.exec.PumpStreamHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MathTypeUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(MathTypeUtil.class);
|
||||||
|
private static OfficeCoreConfig officeCoreConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setOfficeCoreConfig(OfficeCoreConfig officeCoreConfig) {
|
||||||
|
MathTypeUtil.officeCoreConfig = officeCoreConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
HashMap<String, String> map = new HashMap<String, String>();
|
||||||
|
for (int i = 1; i < 927; ++i) {
|
||||||
|
map.put("jby_" + i, "/Users/hking/test/embeddings/oleObject" + i + ".bin");
|
||||||
|
}
|
||||||
|
MathTypeUtil.mathTypeToMathMl(map);
|
||||||
|
System.out.println(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mathTypeToMathMl(Map<String, String> formulaMap) {
|
||||||
|
if (!MapUtils.isEmpty(formulaMap)) {
|
||||||
|
log.info("mathtype\u8f6cmathml\u6570\u91cf:{}", (Object)formulaMap.size());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Collection<Map<String, String>> maps = MapUtil.splitMap(formulaMap, 500);
|
||||||
|
maps.forEach(m -> {
|
||||||
|
Path tempJsonFile;
|
||||||
|
if (MapUtils.isEmpty((Map)formulaMap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CommandLine cmdLine = CommandLine.parse((String)"ruby");
|
||||||
|
cmdLine.addArgument(officeCoreConfig.getTransformScriptPath() + "/mathtype2mathml/batch_converter.rb");
|
||||||
|
String data = JSONObject.toJSONString((Object)m);
|
||||||
|
try {
|
||||||
|
tempJsonFile = Files.createTempFile("mathtype_input-", ".json", new FileAttribute[0]);
|
||||||
|
Files.write(tempJsonFile, data.getBytes(StandardCharsets.UTF_8), new OpenOption[0]);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("\u521b\u5efa\u4e34\u65f6JSON\u6587\u4ef6\u5931\u8d25", (Throwable)e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Path finalTempJsonFile = tempJsonFile;
|
||||||
|
cmdLine.addArgument(tempJsonFile.toString());
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
PumpStreamHandler streamHandler = new PumpStreamHandler((OutputStream)output, (OutputStream)output);
|
||||||
|
DefaultExecutor executor = new DefaultExecutor();
|
||||||
|
executor.setStreamHandler((ExecuteStreamHandler)streamHandler);
|
||||||
|
long ts = System.currentTimeMillis();
|
||||||
|
try {
|
||||||
|
executor.execute(cmdLine);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("/mathtype2mathml/batch_converter.rb error, \n\u547d\u4ee4:{}, \n\u8f93\u51fa:{}", new Object[]{cmdLine, output, e});
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(finalTempJsonFile);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.warn("\u5220\u9664JSON\u4e34\u65f6\u6587\u4ef6\u5931\u8d25: {}", (Object)finalTempJsonFile, (Object)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String fullOutput = output.toString();
|
||||||
|
log.info("/mathtype2mathml/batch_converter.rb end, \u6570\u91cf:{}, \u8017\u65f6:{}\u6beb\u79d2", (Object)m.size(), (Object)(System.currentTimeMillis() - ts));
|
||||||
|
String[] lines = fullOutput.split("\n");
|
||||||
|
String jbyOutput = Arrays.stream(lines).filter(f -> f.startsWith("JBY_RUBY_")).collect(Collectors.joining());
|
||||||
|
String result = jbyOutput.replace("JBY_RUBY_", "");
|
||||||
|
result = result.replace("display=\\\"block\\", "display=\\\"inline\\");
|
||||||
|
result = result.replace("~", "JBYSIMJBY");
|
||||||
|
JSONObject rootObj = JSON.parseObject((String)result);
|
||||||
|
JSONObject results = rootObj.getJSONObject("results");
|
||||||
|
HashMap<String, String> resultMap = new HashMap<String, String>();
|
||||||
|
for (String key : results.keySet()) {
|
||||||
|
JSONObject item = results.getJSONObject(key);
|
||||||
|
String mathml = item.getString("mathml");
|
||||||
|
resultMap.put(key, mathml);
|
||||||
|
}
|
||||||
|
formulaMap.putAll(resultMap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("MathType\u8f6cMathMl\u51fa\u73b0\u5f02\u5e38", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
109
java-src/utils/NumPr.java
Normal file
109
java-src/utils/NumPr.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class NumPr {
|
||||||
|
private BigInteger start;
|
||||||
|
private BigInteger current;
|
||||||
|
private String numFmt;
|
||||||
|
private String lvlText;
|
||||||
|
|
||||||
|
public NumPr() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumPr(BigInteger start, BigInteger current, String numFmt, String lvlText) {
|
||||||
|
this.start = start;
|
||||||
|
this.current = current;
|
||||||
|
this.numFmt = numFmt;
|
||||||
|
this.lvlText = lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getStart() {
|
||||||
|
return this.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCurrent() {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNumFmt() {
|
||||||
|
return this.numFmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLvlText() {
|
||||||
|
return this.lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStart(BigInteger start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrent(BigInteger current) {
|
||||||
|
this.current = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumFmt(String numFmt) {
|
||||||
|
this.numFmt = numFmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLvlText(String lvlText) {
|
||||||
|
this.lvlText = lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof NumPr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NumPr other = (NumPr)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BigInteger this$start = this.getStart();
|
||||||
|
BigInteger other$start = other.getStart();
|
||||||
|
if (this$start == null ? other$start != null : !((Object)this$start).equals(other$start)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BigInteger this$current = this.getCurrent();
|
||||||
|
BigInteger other$current = other.getCurrent();
|
||||||
|
if (this$current == null ? other$current != null : !((Object)this$current).equals(other$current)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$numFmt = this.getNumFmt();
|
||||||
|
String other$numFmt = other.getNumFmt();
|
||||||
|
if (this$numFmt == null ? other$numFmt != null : !this$numFmt.equals(other$numFmt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$lvlText = this.getLvlText();
|
||||||
|
String other$lvlText = other.getLvlText();
|
||||||
|
return !(this$lvlText == null ? other$lvlText != null : !this$lvlText.equals(other$lvlText));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof NumPr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
BigInteger $start = this.getStart();
|
||||||
|
result = result * 59 + ($start == null ? 43 : ((Object)$start).hashCode());
|
||||||
|
BigInteger $current = this.getCurrent();
|
||||||
|
result = result * 59 + ($current == null ? 43 : ((Object)$current).hashCode());
|
||||||
|
String $numFmt = this.getNumFmt();
|
||||||
|
result = result * 59 + ($numFmt == null ? 43 : $numFmt.hashCode());
|
||||||
|
String $lvlText = this.getLvlText();
|
||||||
|
result = result * 59 + ($lvlText == null ? 43 : $lvlText.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "NumPr(start=" + this.getStart() + ", current=" + this.getCurrent() + ", numFmt=" + this.getNumFmt() + ", lvlText=" + this.getLvlText() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
235
java-src/utils/OfficeFileUtil.java
Normal file
235
java-src/utils/OfficeFileUtil.java
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* cn.hutool.core.io.FileUtil
|
||||||
|
* com.alibaba.fastjson.JSON
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* com.alibaba.fastjson.TypeReference
|
||||||
|
* com.jby.common.exceptions.BusinessException
|
||||||
|
* com.jby.common.util.FileUtil
|
||||||
|
* com.jby.common.util.HttpClientUtil
|
||||||
|
* com.jby.common.util.RequestHeaderUtil
|
||||||
|
* com.jby.oss.domain.param.OssParam
|
||||||
|
* com.jby.system.enums.AttachmentModuleEnum
|
||||||
|
* org.apache.poi.xwpf.usermodel.XWPFDocument
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
* org.springframework.beans.factory.annotation.Autowired
|
||||||
|
* org.springframework.stereotype.Component
|
||||||
|
* org.springframework.web.multipart.MultipartFile
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.alibaba.fastjson.TypeReference;
|
||||||
|
import com.jby.common.exceptions.BusinessException;
|
||||||
|
import com.jby.common.util.HttpClientUtil;
|
||||||
|
import com.jby.common.util.RequestHeaderUtil;
|
||||||
|
import com.jby.officecore.config.OfficeCoreConfig;
|
||||||
|
import com.jby.officecore.utils.OssServiceUtil;
|
||||||
|
import com.jby.oss.domain.param.OssParam;
|
||||||
|
import com.jby.system.enums.AttachmentModuleEnum;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.OpenOption;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class OfficeFileUtil {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OfficeFileUtil.class);
|
||||||
|
private static OfficeCoreConfig officeCoreConfig;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setOfficeCoreConfig(OfficeCoreConfig officeCoreConfig) {
|
||||||
|
OfficeFileUtil.officeCoreConfig = officeCoreConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File writeFile(String content, String savePath) {
|
||||||
|
FileWriter fw = null;
|
||||||
|
BufferedWriter bW = null;
|
||||||
|
try {
|
||||||
|
File file = com.jby.common.util.FileUtil.getFile((String)savePath);
|
||||||
|
fw = new FileWriter(file);
|
||||||
|
bW = new BufferedWriter(fw);
|
||||||
|
bW.write(content);
|
||||||
|
File file2 = file;
|
||||||
|
return file2;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5199\u5165\u6587\u4ef6\u5f02\u5e38, savePath:{}", (Object)savePath, (Object)e);
|
||||||
|
throw new BusinessException("\u5199\u5165\u6587\u4ef6\u5f02\u5e38");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (bW != null) {
|
||||||
|
try {
|
||||||
|
bW.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fw != null) {
|
||||||
|
try {
|
||||||
|
fw.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File writeWord(XWPFDocument document, String savePath) {
|
||||||
|
OutputStream os = null;
|
||||||
|
try {
|
||||||
|
File file = com.jby.common.util.FileUtil.getFile((String)savePath);
|
||||||
|
os = Files.newOutputStream(file.toPath(), new OpenOption[0]);
|
||||||
|
document.write(os);
|
||||||
|
File file2 = file;
|
||||||
|
return file2;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u751f\u6210word\u6587\u4ef6\u5f02\u5e38, savePath:{}", (Object)savePath, (Object)e);
|
||||||
|
throw new BusinessException("\u751f\u6210word\u6587\u4ef6\u5f02\u5e38\uff01");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Objects.nonNull(os)) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(document)) {
|
||||||
|
try {
|
||||||
|
document.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File writeFile(MultipartFile file, String savePath) {
|
||||||
|
try {
|
||||||
|
return OfficeFileUtil.writeFile(file.getInputStream(), savePath);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5199\u5165\u6587\u4ef6\u5f02\u5e38, savePath:{}", (Object)savePath, (Object)e);
|
||||||
|
throw new BusinessException("\u5199\u5165\u6587\u4ef6\u5f02\u5e38");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File writeFile(InputStream is, String savePath) {
|
||||||
|
FileOutputStream os = null;
|
||||||
|
try {
|
||||||
|
int length;
|
||||||
|
File saveFile = com.jby.common.util.FileUtil.getFile((String)savePath);
|
||||||
|
os = new FileOutputStream(saveFile);
|
||||||
|
byte[] buffer = new byte[15360];
|
||||||
|
while ((length = is.read(buffer)) != -1) {
|
||||||
|
((OutputStream)os).write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
os.flush();
|
||||||
|
File file = saveFile;
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5199\u5165\u6587\u4ef6\u5f02\u5e38, savePath:{}", (Object)savePath, (Object)e);
|
||||||
|
throw new BusinessException("\u5199\u5165\u6587\u4ef6\u5f02\u5e38");
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (os != null) {
|
||||||
|
try {
|
||||||
|
((OutputStream)os).close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error", (Throwable)e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WARNING - Removed try catching itself - possible behaviour change.
|
||||||
|
*/
|
||||||
|
public static String mf2svg(InputStream is, String imgPath) {
|
||||||
|
HashMap<String, Object> params = null;
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
File localFile = com.jby.common.util.FileUtil.getFile((String)imgPath);
|
||||||
|
FileUtil.writeFromStream((InputStream)is, (File)localFile);
|
||||||
|
String accessUrl = OssServiceUtil.uploadFile(new OssParam().setModule(AttachmentModuleEnum.OFFICE_IMG.getModule()).setFile(com.jby.common.util.FileUtil.getMultipartFile((String)localFile.getName(), (File)localFile)));
|
||||||
|
params = new HashMap<String, Object>();
|
||||||
|
params.put("fileUrl", accessUrl);
|
||||||
|
params.put("refreshImg", false);
|
||||||
|
Map headers = RequestHeaderUtil.getAuthHeaders();
|
||||||
|
result = HttpClientUtil.doPost((String)officeCoreConfig.getMf2SvgApi(), params, (Map)headers);
|
||||||
|
JSONObject json = (JSONObject)HttpClientUtil.parseResult((String)result, (TypeReference)new TypeReference<JSONObject>(){});
|
||||||
|
String string = Objects.isNull(json) ? null : json.getString("svgUrl");
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mf2svg exception, imgPath:{}, params:{}, result:{}", new Object[]{imgPath, JSON.toJSONString(params), result, e});
|
||||||
|
String string = null;
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (is != null) {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
log.error("close error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String mf2svg(InputStream is, String ossFolder, String imgName) {
|
||||||
|
HashMap<String, Object> params = null;
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
String imgUrl = OssServiceUtil.uploadStream(new OssParam().setModule(AttachmentModuleEnum.OFFICE_IMG.getModule()).setFolder(ossFolder).setFileName(imgName).setInputStream(is));
|
||||||
|
params = new HashMap<String, Object>();
|
||||||
|
params.put("fileUrl", imgUrl);
|
||||||
|
params.put("refreshImg", false);
|
||||||
|
Map headers = RequestHeaderUtil.getAuthHeaders();
|
||||||
|
result = HttpClientUtil.doPost((String)officeCoreConfig.getMf2SvgApi(), params, (Map)headers);
|
||||||
|
JSONObject json = (JSONObject)HttpClientUtil.parseResult((String)result, (TypeReference)new TypeReference<JSONObject>(){});
|
||||||
|
return Objects.isNull(json) ? null : json.getString("svgUrl");
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("mf2svg exception, ossFolder:{}, imgName:{}, params:{}, result:{}", new Object[]{ossFolder, imgName, JSON.toJSONString(params), result, e});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1791
java-src/utils/WordParseUtil.java
Normal file
1791
java-src/utils/WordParseUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
46
java-src/utils/finder/CtObjectFinder.java
Normal file
46
java-src/utils/finder/CtObjectFinder.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils.finder;
|
||||||
|
|
||||||
|
import com.jby.officecore.domain.bo.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
|
||||||
|
public class CtObjectFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtObjectFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof CTOLEObject) {
|
||||||
|
CTOLEObject ctoleObject = (CTOLEObject)o;
|
||||||
|
RelationshipsPart rsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
if (Objects.nonNull(ctoleObject.getId())) {
|
||||||
|
Part part = rsPart.getPart(ctoleObject.getId());
|
||||||
|
String binFilePath = part.getPartName().getURI().getPath();
|
||||||
|
String placeholder = this.parseBO.getMathTypePlaceholder(ctoleObject.getId());
|
||||||
|
this.parseBO.getMathTypeMap().put(placeholder, this.parseBO.getTempDir() + "/temp" + binFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
121
java-src/utils/finder/CtTextBoxFinder.java
Normal file
121
java-src/utils/finder/CtTextBoxFinder.java
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TextUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.mce.AlternateContent
|
||||||
|
* org.docx4j.mce.AlternateContent$Fallback
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.vml.CTGroup
|
||||||
|
* org.docx4j.vml.CTRoundRect
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.CTTextbox
|
||||||
|
* org.docx4j.vml.VmlShapeElements
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils.finder;
|
||||||
|
|
||||||
|
import com.jby.officecore.domain.bo.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TextUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.mce.AlternateContent;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.vml.CTGroup;
|
||||||
|
import org.docx4j.vml.CTRoundRect;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.CTTextbox;
|
||||||
|
import org.docx4j.vml.VmlShapeElements;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CtTextBoxFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtTextBoxFinder.class);
|
||||||
|
private WordprocessingMLPackage wordMlPackage;
|
||||||
|
private ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtTextBoxFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof Pict) {
|
||||||
|
boolean isFirstRun = true;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
R sourceR = factory.createR();
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
List anyAndAny = pict.getAnyAndAny();
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
if (!(object instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement jbe = (JAXBElement)object;
|
||||||
|
this.findTextBox(jbe.getValue(), stringBuilder, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)stringBuilder.toString())) {
|
||||||
|
AlternateContent.Fallback fallback = (AlternateContent.Fallback)pict.getParent();
|
||||||
|
AlternateContent alternateContent = (AlternateContent)fallback.getParent();
|
||||||
|
R r = (R)alternateContent.getParent();
|
||||||
|
Text text = factory.createText();
|
||||||
|
text.setValue(stringBuilder + "JBY_TEXTBOX_JBY");
|
||||||
|
r.getContent().set(0, text);
|
||||||
|
r.setRPr(sourceR.getRPr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findTextBox(Object object, StringBuilder sb, boolean isFirstRun, R sourceR) {
|
||||||
|
block8: {
|
||||||
|
List egShapeElements;
|
||||||
|
if (object instanceof CTShape || object instanceof CTRoundRect) {
|
||||||
|
VmlShapeElements vmlShapeElements = (VmlShapeElements)object;
|
||||||
|
egShapeElements = vmlShapeElements.getEGShapeElements();
|
||||||
|
for (JAXBElement egShapeElement : egShapeElements) {
|
||||||
|
if (!(egShapeElement.getValue() instanceof CTTextbox)) continue;
|
||||||
|
CTTextbox ctTextbox = (CTTextbox)egShapeElement.getValue();
|
||||||
|
CTTxbxContent txbxContent = ctTextbox.getTxbxContent();
|
||||||
|
for (int i = 0; i < txbxContent.getContent().size(); ++i) {
|
||||||
|
Object obj = txbxContent.getContent().get(i);
|
||||||
|
String text = TextUtils.getText(obj);
|
||||||
|
if (txbxContent.getContent().size() > 1 && i != txbxContent.getContent().size() - 1) {
|
||||||
|
text = text + "JBY_TEXTBOX_JBYBR";
|
||||||
|
}
|
||||||
|
sb.append(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(object instanceof CTGroup)) break block8;
|
||||||
|
CTGroup ctGroup = (CTGroup)object;
|
||||||
|
egShapeElements = ctGroup.getEGShapeElements();
|
||||||
|
boolean isCtShape = egShapeElements.stream().anyMatch(f -> f.getValue() instanceof CTShape);
|
||||||
|
if (isCtShape) {
|
||||||
|
for (int i = egShapeElements.size() - 1; i >= 0; --i) {
|
||||||
|
this.findTextBox(((JAXBElement)egShapeElements.get(i)).getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (JAXBElement egShapeElement : egShapeElements) {
|
||||||
|
this.findTextBox(egShapeElement.getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
427
java-src/utils/finder/CtoMathFinder.java
Normal file
427
java-src/utils/finder/CtoMathFinder.java
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.XmlUtils
|
||||||
|
* org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTTextboxInfo
|
||||||
|
* org.docx4j.math.CTOMath
|
||||||
|
* org.docx4j.math.CTOMathPara
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.CTVerticalAlignRun
|
||||||
|
* org.docx4j.wml.FldChar
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.P
|
||||||
|
* org.docx4j.wml.PPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr$Ilvl
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.RPrAbstract
|
||||||
|
* org.docx4j.wml.STFldCharType
|
||||||
|
* org.docx4j.wml.Style
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.docx4j.wml.U
|
||||||
|
* org.docx4j.wml.UnderlineEnumeration
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils.finder;
|
||||||
|
|
||||||
|
import com.jby.officecore.domain.bo.ParseBO;
|
||||||
|
import com.jby.officecore.utils.EqToLatexUtil;
|
||||||
|
import com.jby.officecore.utils.NumPr;
|
||||||
|
import com.jby.officecore.utils.WordParseUtil;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.XmlUtils;
|
||||||
|
import org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTTextboxInfo;
|
||||||
|
import org.docx4j.math.CTOMath;
|
||||||
|
import org.docx4j.math.CTOMathPara;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.CTVerticalAlignRun;
|
||||||
|
import org.docx4j.wml.FldChar;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.P;
|
||||||
|
import org.docx4j.wml.PPr;
|
||||||
|
import org.docx4j.wml.PPrBase;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.RPrAbstract;
|
||||||
|
import org.docx4j.wml.STFldCharType;
|
||||||
|
import org.docx4j.wml.Style;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.docx4j.wml.U;
|
||||||
|
import org.docx4j.wml.UnderlineEnumeration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CtoMathFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtoMathFinder.class);
|
||||||
|
private ParseBO parseBO;
|
||||||
|
private static final Pattern CIRCLED_NUMBER_PATTERN = Pattern.compile("^\\s*eq\\s*\\\\o\\((?:\u25cb,\\\\s\\\\up\\d+\\((\\d+)\\)|\\\\s\\\\up\\d+\\((\\d+)\\),\u25cb)\\)");
|
||||||
|
|
||||||
|
public CtoMathFinder(ParseBO parseBO) {
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof P) {
|
||||||
|
Text text;
|
||||||
|
Style style;
|
||||||
|
CTTxbxContent ctTxbxContent;
|
||||||
|
StringBuilder eqCode = new StringBuilder();
|
||||||
|
StringBuilder originEqCode = new StringBuilder();
|
||||||
|
boolean inFiled = false;
|
||||||
|
boolean isEq = false;
|
||||||
|
boolean superScriptFlag = false;
|
||||||
|
boolean subScriptFlag = false;
|
||||||
|
ArrayList<Integer> fieldRunIndexs = new ArrayList<Integer>();
|
||||||
|
P paragraph = (P)o;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
boolean isNumPr = true;
|
||||||
|
if (paragraph.getParent() instanceof CTTxbxContent && (ctTxbxContent = (CTTxbxContent)paragraph.getParent()).getParent() instanceof CTTextboxInfo) {
|
||||||
|
isNumPr = false;
|
||||||
|
}
|
||||||
|
PPr pPr = paragraph.getPPr();
|
||||||
|
PPrBase.NumPr numPr = null;
|
||||||
|
if (Objects.nonNull(pPr)) {
|
||||||
|
numPr = pPr.getNumPr();
|
||||||
|
}
|
||||||
|
if (Objects.isNull(numPr) && Objects.nonNull(pPr) && Objects.nonNull(pPr.getPStyle()) && Objects.nonNull(style = this.parseBO.getStyleDefinitionsPart().getStyleById(pPr.getPStyle().getVal())) && Objects.nonNull(style.getPPr()) && Objects.nonNull(style.getPPr().getNumPr()) && Objects.isNull((numPr = style.getPPr().getNumPr()).getIlvl())) {
|
||||||
|
PPrBase.NumPr.Ilvl ilvl = new PPrBase.NumPr.Ilvl();
|
||||||
|
ilvl.setVal(BigInteger.ZERO);
|
||||||
|
numPr.setIlvl(ilvl);
|
||||||
|
}
|
||||||
|
if (isNumPr && Objects.nonNull(numPr) && Objects.nonNull(numPr.getNumId()) && Objects.nonNull(numPr.getIlvl())) {
|
||||||
|
String numPrKey = numPr.getNumId().getVal() + "_" + numPr.getIlvl().getVal();
|
||||||
|
Map<String, NumPr> numberingDefinitionsMap = this.parseBO.getNumberingDefinitionsMap();
|
||||||
|
if (numberingDefinitionsMap.containsKey(numPrKey)) {
|
||||||
|
NumPr currentNumPr = numberingDefinitionsMap.get(numPrKey);
|
||||||
|
String lvlText = currentNumPr.getLvlText();
|
||||||
|
for (int i = 0; i <= numPr.getIlvl().getVal().intValue(); ++i) {
|
||||||
|
NumPr numPrDefine;
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
currentNumPr.setCurrent(currentNumPr.getCurrent().add(BigInteger.ONE));
|
||||||
|
}
|
||||||
|
String rep = String.valueOf((numPrDefine = numberingDefinitionsMap.get(numPr.getNumId().getVal() + "_" + i)).getCurrent().compareTo(BigInteger.ZERO) == 0 ? numPrDefine.getStart() : numPrDefine.getCurrent());
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
switch (currentNumPr.getNumFmt()) {
|
||||||
|
case "UPPER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DECIMAL_ZERO": {
|
||||||
|
rep = CtoMathFinder.strToDecimalZero(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPPER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep).toUpperCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep).toLowerCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CHINESE_COUNTING":
|
||||||
|
case "CHINESE_COUNTING_THOUSAND": {
|
||||||
|
rep = this.strToChineseCounting(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lvlText = lvlText.replace("%" + (i + 1), rep);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)lvlText)) {
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(lvlText + " ");
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().add(0, run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < paragraph.getContent().size(); ++i) {
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
Object content = paragraph.getContent().get(i);
|
||||||
|
if (content instanceof JAXBElement && ((jaxbElement = (JAXBElement)content).getValue() instanceof CTOMath || jaxbElement.getValue() instanceof CTOMathPara)) {
|
||||||
|
String placeholder = "";
|
||||||
|
if (Boolean.TRUE.equals(this.parseBO.getNewParseVersion())) {
|
||||||
|
String omml = XmlUtils.marshaltoString((Object)jaxbElement);
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, omml);
|
||||||
|
} else {
|
||||||
|
String mathMl = WordParseUtil.getMathMl(jaxbElement, this.parseBO.getTransformer());
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, mathMl);
|
||||||
|
}
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(placeholder);
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().set(i, run);
|
||||||
|
}
|
||||||
|
if (!(content instanceof R)) continue;
|
||||||
|
R run = (R)content;
|
||||||
|
for (int j = 0; j < run.getContent().size(); ++j) {
|
||||||
|
Object runC = run.getContent().get(j);
|
||||||
|
if (!(runC instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement runCj = (JAXBElement)runC;
|
||||||
|
if (runCj.getValue() instanceof FldChar) {
|
||||||
|
FldChar fldChar = (FldChar)runCj.getValue();
|
||||||
|
if (fldChar.getFldCharType().equals((Object)STFldCharType.BEGIN)) {
|
||||||
|
eqCode = new StringBuilder();
|
||||||
|
originEqCode = new StringBuilder();
|
||||||
|
inFiled = true;
|
||||||
|
isEq = false;
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
fieldRunIndexs = new ArrayList();
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && fldChar.getFldCharType().equals((Object)STFldCharType.END)) {
|
||||||
|
if (isEq) {
|
||||||
|
String placeholder;
|
||||||
|
Matcher matcher = CIRCLED_NUMBER_PATTERN.matcher(originEqCode);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String numStr = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder() + "JBYEQNUM";
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, CtoMathFinder.getCircledNumber(numStr));
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text2 = factory.createText();
|
||||||
|
text2.setValue(placeholder);
|
||||||
|
runN.getContent().add(text2);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)eqCode.toString())) {
|
||||||
|
String latex = EqToLatexUtil.convert(eqCode.toString());
|
||||||
|
latex = latex.replaceAll("<", "<").replaceAll(">", ">");
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder();
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, latex);
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text3 = factory.createText();
|
||||||
|
text3.setValue(placeholder);
|
||||||
|
runN.getContent().add(text3);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
isEq = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && isEq && !fldChar.getFldCharType().value().equals("separate")) continue;
|
||||||
|
}
|
||||||
|
if (!inFiled || !(runCj.getValue() instanceof Text)) continue;
|
||||||
|
CTVerticalAlignRun vertAlign = Optional.ofNullable(run.getRPr()).map(RPrAbstract::getVertAlign).orElse(null);
|
||||||
|
String value = ((Text)runCj.getValue()).getValue();
|
||||||
|
originEqCode.append(value);
|
||||||
|
U u = Optional.ofNullable(run.getRPr()).filter(f -> Objects.nonNull(f.getU()) && !UnderlineEnumeration.NONE.equals((Object)f.getU().getVal()) && Objects.nonNull(f.getU().getVal())).map(RPrAbstract::getU).orElse(null);
|
||||||
|
if (StringUtils.isBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = value.replace("\u3000", "JBYU1JBYU").replace(" ", "JBYU1JBYU");
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = "JBYU2BEGIN" + value + "JBYU2END";
|
||||||
|
} else if (StringUtils.isBlank((CharSequence)value)) {
|
||||||
|
// empty if block
|
||||||
|
}
|
||||||
|
String trimmedOrigin = CtoMathFinder.extractEqPrefix(originEqCode);
|
||||||
|
if (trimmedOrigin.startsWith("eq")) {
|
||||||
|
isEq = true;
|
||||||
|
}
|
||||||
|
if (isEq) {
|
||||||
|
if (CollectionUtils.isNotEmpty(fieldRunIndexs)) {
|
||||||
|
fieldRunIndexs.forEach(m -> paragraph.getContent().set((int)m, factory.createR()));
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(vertAlign)) {
|
||||||
|
String newContent;
|
||||||
|
String tmp;
|
||||||
|
if ("superscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (superScriptFlag && (start = eqCode.lastIndexOf("\\s\\up4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\up4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\up4(" + value + ")";
|
||||||
|
}
|
||||||
|
superScriptFlag = true;
|
||||||
|
subScriptFlag = false;
|
||||||
|
} else if ("subscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (subScriptFlag && (start = eqCode.lastIndexOf("\\s\\do4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\do4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\do4(" + value + ")";
|
||||||
|
}
|
||||||
|
subScriptFlag = true;
|
||||||
|
superScriptFlag = false;
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
eqCode.append(value);
|
||||||
|
paragraph.getContent().set(i, factory.createR());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (trimmedOrigin.length() < 2 || "eq".startsWith(trimmedOrigin)) {
|
||||||
|
eqCode.append(value);
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String strToUpperRoman(String str) {
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 1 || num > 3999) {
|
||||||
|
return "I";
|
||||||
|
}
|
||||||
|
int[] values = new int[]{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
|
||||||
|
String[] symbols = new String[]{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
|
||||||
|
StringBuilder roman = new StringBuilder();
|
||||||
|
for (int i = 0; i < values.length; ++i) {
|
||||||
|
while (num >= values[i]) {
|
||||||
|
roman.append(symbols[i]);
|
||||||
|
num -= values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roman.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToDecimalZero(String str) {
|
||||||
|
if (str.length() == 1) {
|
||||||
|
return "0" + str;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToLowerLetter(String str) {
|
||||||
|
int n = Integer.parseInt(str);
|
||||||
|
int length = (n - 1) / 26 + 1;
|
||||||
|
char baseChar = (char)(97 + (n - 1) % 26);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
sb.append(baseChar);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String strToChineseCounting(String str) {
|
||||||
|
String s;
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 0 || num >= 1000000000) {
|
||||||
|
throw new IllegalArgumentException("\u4ec5\u652f\u6301 0 \u5230 999,999,999");
|
||||||
|
}
|
||||||
|
if (num == 0) {
|
||||||
|
return "\u3007";
|
||||||
|
}
|
||||||
|
char[] digits = new char[]{'\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d'};
|
||||||
|
String[] units = new String[]{"", "\u5341", "\u767e", "\u5343"};
|
||||||
|
String[] bigUnits = new String[]{"", "\u4e07", "\u4ebf"};
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int yi = num / 100000000;
|
||||||
|
int wan = num / 10000 % 10000;
|
||||||
|
int ge = num % 10000;
|
||||||
|
if (yi > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(yi, digits, units)).append(bigUnits[2]);
|
||||||
|
if (wan == 0 && ge > 0) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wan > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(wan, digits, units)).append(bigUnits[1]);
|
||||||
|
if (ge > 0 && ge < 1000) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ge > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(ge, digits, units));
|
||||||
|
}
|
||||||
|
if ((s = result.toString()).startsWith("\u4e00\u5341")) {
|
||||||
|
s = s.substring(1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertSegment(int n, char[] digits, String[] units) {
|
||||||
|
if (n == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int power = 3 - i;
|
||||||
|
int base = (int)Math.pow(10.0, power);
|
||||||
|
int digit = n / base % 10;
|
||||||
|
if (digit != 0) {
|
||||||
|
sb.append(digits[digit]).append(units[power]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sb.length() <= 0 || n % base == 0 || power <= 0) continue;
|
||||||
|
sb.append('\u3007');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCircledNumber(String num) {
|
||||||
|
int n = Integer.parseInt(num);
|
||||||
|
if (n >= 1 && n <= 20) {
|
||||||
|
return String.valueOf((char)(9312 + n - 1));
|
||||||
|
}
|
||||||
|
if (n >= 21 && n <= 35) {
|
||||||
|
return String.valueOf((char)(12881 + n - 21));
|
||||||
|
}
|
||||||
|
if (n >= 36 && n <= 40) {
|
||||||
|
return String.valueOf((char)(12977 + n - 36));
|
||||||
|
}
|
||||||
|
if (n >= 41 && n <= 50) {
|
||||||
|
return String.valueOf((char)(12982 + n - 41));
|
||||||
|
}
|
||||||
|
log.info("\u83b7\u53d6\u5e26\u5708\u5b57\u7b26\u5931\u8d25 {}", (Object)num);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractEqPrefix(StringBuilder originEqCode) {
|
||||||
|
int i;
|
||||||
|
String s = originEqCode.toString().trim();
|
||||||
|
for (i = 0; i < s.length() && s.charAt(i) == '{'; ++i) {
|
||||||
|
}
|
||||||
|
return s.substring(i).trim().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
434
java-src/utils/finder/DrawingPictFinder.java
Normal file
434
java-src/utils/finder/DrawingPictFinder.java
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.excel.util.StringUtils
|
||||||
|
* com.jby.common.util.UniqueUtil
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.dml.GraphicData
|
||||||
|
* org.docx4j.dml.picture.Pic
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Anchor
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Inline
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.CTImageData
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
* org.docx4j.wml.CTObject
|
||||||
|
* org.docx4j.wml.Drawing
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.jby.officecore.utils.finder;
|
||||||
|
|
||||||
|
import com.alibaba.excel.util.StringUtils;
|
||||||
|
import com.jby.common.util.UniqueUtil;
|
||||||
|
import com.jby.officecore.domain.bo.ImageBO;
|
||||||
|
import com.jby.officecore.domain.bo.ParseBO;
|
||||||
|
import com.jby.officecore.enums.AttrEnum;
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.dml.GraphicData;
|
||||||
|
import org.docx4j.dml.picture.Pic;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Anchor;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Inline;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.CTImageData;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
import org.docx4j.wml.CTObject;
|
||||||
|
import org.docx4j.wml.Drawing;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DrawingPictFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DrawingPictFinder.class);
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
private static final double CROP_SCALE = 1.52587890625;
|
||||||
|
|
||||||
|
public DrawingPictFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
List contentList;
|
||||||
|
List anyAndAny;
|
||||||
|
R r;
|
||||||
|
if (o instanceof Drawing && ((Drawing)o).getParent() instanceof R) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Drawing drawing = (Drawing)o;
|
||||||
|
List anchorOrInline = drawing.getAnchorOrInline();
|
||||||
|
if (CollectionUtils.isNotEmpty((Collection)anchorOrInline)) {
|
||||||
|
Object anchorOrInlineObj = anchorOrInline.get(0);
|
||||||
|
GraphicData graphicData = null;
|
||||||
|
long cx = 0L;
|
||||||
|
long cy = 0L;
|
||||||
|
if (anchorOrInlineObj instanceof Inline) {
|
||||||
|
Inline inline = (Inline)anchorOrInlineObj;
|
||||||
|
graphicData = inline.getGraphic().getGraphicData();
|
||||||
|
cx = inline.getExtent().getCx();
|
||||||
|
cy = inline.getExtent().getCy();
|
||||||
|
} else if (anchorOrInlineObj instanceof Anchor) {
|
||||||
|
Anchor anchor = (Anchor)anchorOrInlineObj;
|
||||||
|
graphicData = anchor.getGraphic().getGraphicData();
|
||||||
|
cx = anchor.getExtent().getCx();
|
||||||
|
cy = anchor.getExtent().getCy();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(graphicData) && "http://schemas.openxmlformats.org/drawingml/2006/picture".equals(graphicData.getUri())) {
|
||||||
|
for (Object object : graphicData.getAny()) {
|
||||||
|
String path;
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
if (!(object instanceof JAXBElement) || !((jaxbElement = (JAXBElement)object).getValue() instanceof Pic)) continue;
|
||||||
|
Pic pic = (Pic)jaxbElement.getValue();
|
||||||
|
String imageDataId = null;
|
||||||
|
int cropLeft = 0;
|
||||||
|
int cropTop = 0;
|
||||||
|
int cropRight = 0;
|
||||||
|
int cropBottom = 0;
|
||||||
|
int rotation = 0;
|
||||||
|
if (Objects.nonNull(pic.getBlipFill())) {
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getBlip())) {
|
||||||
|
imageDataId = pic.getBlipFill().getBlip().getEmbed();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getSrcRect())) {
|
||||||
|
cropLeft = pic.getBlipFill().getSrcRect().getL();
|
||||||
|
cropTop = pic.getBlipFill().getSrcRect().getT();
|
||||||
|
cropRight = pic.getBlipFill().getSrcRect().getR();
|
||||||
|
cropBottom = pic.getBlipFill().getSrcRect().getB();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getSpPr()) && Objects.nonNull(pic.getSpPr().getXfrm())) {
|
||||||
|
rotation = pic.getSpPr().getXfrm().getRot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Object> map = this.dealCropRotate(imageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
Object heightObj = map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
double width = Objects.nonNull(widthObj) ? Double.parseDouble(widthObj.toString()) : (double)Math.round((double)cx / 9525.0);
|
||||||
|
double height = Objects.nonNull(heightObj) ? Double.parseDouble(heightObj.toString()) : (double)Math.round((double)cy / 9525.0);
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u56fe\u7247Part\uff0cimageDataId={}", (Object)imageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(imageDataId);
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(width).setHeight(height).setPictureUrl(path);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
R r2 = (R)drawing.getParent();
|
||||||
|
List contentList2 = r2.getContent();
|
||||||
|
int drawingIndex = contentList2.indexOf(drawing);
|
||||||
|
if (drawingIndex != -1) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
contentList2.set(drawingIndex, text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.warn("\u672a\u627e\u5230drawing\u5bf9\u8c61\u5728\u7236\u5143\u7d20\u4e2d\u7684\u7d22\u5f15\uff0c\u8df3\u8fc7\u66ff\u6362\u64cd\u4f5c");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof Pict && ((Pict)o).getParent() instanceof R) {
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
r = (R)pict.getParent();
|
||||||
|
anyAndAny = pict.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int pictIndex = contentList.indexOf(pict);
|
||||||
|
if (pictIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, pictIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof CTObject && ((CTObject)o).getParent() instanceof R) {
|
||||||
|
CTObject ctObject = (CTObject)o;
|
||||||
|
r = (R)ctObject.getParent();
|
||||||
|
anyAndAny = ctObject.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int ctObjectIndex = contentList.indexOf(ctObject);
|
||||||
|
if (ctObjectIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, ctObjectIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractValue(String style, String property) {
|
||||||
|
if (style == null || property == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String regex = "\\b" + Pattern.quote(property) + "\\s*:\\s*([\\d.]+[a-zA-Z]*)";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(style);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dealCtShape(List<Object> anyAndAny, R r, int pictIndex) {
|
||||||
|
String ctOleObjectRid = anyAndAny.stream().filter(f -> f instanceof JAXBElement && ((JAXBElement)f).getValue() instanceof CTOLEObject).map(f -> ((CTOLEObject)((JAXBElement)f).getValue()).getId()).filter(Objects::nonNull).findFirst().orElse(null);
|
||||||
|
if (Objects.nonNull(ctOleObjectRid) && Objects.nonNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
} else if (Objects.isNull(ctOleObjectRid) || Objects.isNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
if (Objects.nonNull(ctOleObjectRid)) {
|
||||||
|
this.parseBO.getMathTypeMap().remove(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
}
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
String path;
|
||||||
|
double heightPx;
|
||||||
|
double widthPx;
|
||||||
|
if (!(object instanceof JAXBElement) || !(((JAXBElement)object).getValue() instanceof CTShape)) continue;
|
||||||
|
CTShape ctShape = (CTShape)((JAXBElement)object).getValue();
|
||||||
|
String width = this.extractValue(ctShape.getStyle(), "width");
|
||||||
|
String height = this.extractValue(ctShape.getStyle(), "height");
|
||||||
|
CTImageData ctImageData = ctShape.getEGShapeElements().stream().filter(f -> f.getValue() instanceof CTImageData).map(m -> (CTImageData)m.getValue()).findFirst().orElse(null);
|
||||||
|
if (!Objects.nonNull(ctImageData) || !StringUtils.isNotBlank((CharSequence)width) || !StringUtils.isNotBlank((CharSequence)height)) continue;
|
||||||
|
String ctImageDataId = ctImageData.getId();
|
||||||
|
long cx = 0L;
|
||||||
|
try {
|
||||||
|
double widthPtIn = Double.parseDouble(width.substring(0, width.length() - 2));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
cx = Math.round(widthPtIn * 12700.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
cx = Math.round(widthPtIn * 914400.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ignored) {
|
||||||
|
log.error("extractWidth error");
|
||||||
|
}
|
||||||
|
int cropLeft = this.parseVmlCrop(ctImageData.getCropleft());
|
||||||
|
int cropTop = this.parseVmlCrop(ctImageData.getCroptop());
|
||||||
|
int cropRight = this.parseVmlCrop(ctImageData.getCropright());
|
||||||
|
int cropBottom = this.parseVmlCrop(ctImageData.getCropbottom());
|
||||||
|
String rotRaw = this.extractValue(ctShape.getStyle(), "rotation");
|
||||||
|
int rotation = rotRaw == null ? 0 : (int)Math.round((double)Long.parseLong(rotRaw.replaceAll("[^\\d-]", "")) * 0.91552734375);
|
||||||
|
Map<String, Object> map = this.dealCropRotate(ctImageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
Object heightObj = map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
if (Objects.nonNull(widthObj) && Objects.nonNull(heightObj)) {
|
||||||
|
widthPx = (Double)map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
heightPx = (Double)map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
} else {
|
||||||
|
widthPx = Double.parseDouble(width.replaceAll("pt|in", ""));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0 / 72.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0);
|
||||||
|
}
|
||||||
|
heightPx = Double.parseDouble(height.replaceAll("pt|in", ""));
|
||||||
|
if (height.endsWith("pt")) {
|
||||||
|
heightPx = heightPx * 96.0 / 72.0;
|
||||||
|
} else if (height.endsWith("in")) {
|
||||||
|
heightPx *= 96.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(ctImageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u8001\u7248VML\u56fe\u7247Part\uff0cctImageDataId={}", (Object)ctImageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(widthPx).setHeight(heightPx).setPictureUrl(path);
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(ctImageDataId);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> dealCropRotate(String imageDataId, long cx, int cropLeft, int cropTop, int cropRight, int cropBottom, int rotation) {
|
||||||
|
HashMap<String, Object> result = new HashMap<String, Object>();
|
||||||
|
try {
|
||||||
|
if (Objects.nonNull(imageDataId)) {
|
||||||
|
boolean isRotate;
|
||||||
|
boolean isCrop = cropLeft > 0 || cropTop > 0 || cropRight > 0 || cropBottom > 0;
|
||||||
|
boolean bl = isRotate = rotation != 0;
|
||||||
|
if (isCrop || isRotate) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage)part;
|
||||||
|
BufferedImage targetImage = ImageIO.read(new ByteArrayInputStream(imagePart.getBytes()));
|
||||||
|
if (isCrop) {
|
||||||
|
targetImage = this.cropImage(targetImage, cropLeft, cropTop, cropRight, cropBottom);
|
||||||
|
}
|
||||||
|
if (isRotate) {
|
||||||
|
targetImage = this.rotateImage(targetImage, rotation, cx, result);
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(targetImage)) {
|
||||||
|
String formatName = this.getFormatName(imagePart);
|
||||||
|
String path = "/word/media/" + UniqueUtil.getUuid() + "." + formatName;
|
||||||
|
ImageIO.write((RenderedImage)targetImage, formatName, new File(this.parseBO.getTempDir() + "/temp" + path));
|
||||||
|
result.put("imagePath", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5904\u7406\u56fe\u7247\u88c1\u526a\u4e0e\u65cb\u8f6c\u5f02\u5e38", (Throwable)e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFormatName(BinaryPartAbstractImage imagePart) {
|
||||||
|
String contentType = imagePart.getContentType();
|
||||||
|
String formatName = contentType.contains("png") ? "png" : (contentType.contains("jpeg") ? "jpeg" : (contentType.contains("gif") ? "gif" : (contentType.contains("bmp") ? "bmp" : "png")));
|
||||||
|
return formatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage cropImage(BufferedImage originalImage, int srcRectL, int srcRectT, int srcRectR, int srcRectB) {
|
||||||
|
double leftRatio = srcRectL < 0 ? 0.0 : (double)srcRectL / 100000.0;
|
||||||
|
double topRatio = srcRectT < 0 ? 0.0 : (double)srcRectT / 100000.0;
|
||||||
|
double rightRatio = srcRectR < 0 ? 0.0 : (double)srcRectR / 100000.0;
|
||||||
|
double bottomRatio = srcRectB < 0 ? 0.0 : (double)srcRectB / 100000.0;
|
||||||
|
int originalWidth = originalImage.getWidth();
|
||||||
|
int originalHeight = originalImage.getHeight();
|
||||||
|
int left = (int)Math.floor(leftRatio * (double)originalWidth);
|
||||||
|
int top = (int)Math.floor(topRatio * (double)originalHeight);
|
||||||
|
int right = (int)Math.floor(rightRatio * (double)originalWidth);
|
||||||
|
int bottom = (int)Math.floor(bottomRatio * (double)originalHeight);
|
||||||
|
int cropWidth = Math.max(1, originalWidth - left - right);
|
||||||
|
int cropHeight = Math.max(1, originalHeight - top - bottom);
|
||||||
|
if (left < 0 || top < 0 || left + cropWidth > originalWidth || top + cropHeight > originalHeight) {
|
||||||
|
log.error("\u88c1\u526a\u533a\u57df\u65e0\u6548\u6216\u8d85\u51fa\u56fe\u7247\u8fb9\u754c");
|
||||||
|
}
|
||||||
|
return originalImage.getSubimage(left, top, cropWidth, cropHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage rotateImage(BufferedImage originalImage, long rotation, long currentCx, Map<String, Object> result) {
|
||||||
|
int origWidth = originalImage.getWidth();
|
||||||
|
int origHeight = originalImage.getHeight();
|
||||||
|
if (origWidth <= 0 || origHeight <= 0) {
|
||||||
|
log.error("\u56fe\u7247\u533a\u57df\u65e0\u6548");
|
||||||
|
}
|
||||||
|
double emuPerPx = (double)currentCx / (double)origWidth;
|
||||||
|
if (currentCx == 0L) {
|
||||||
|
emuPerPx = 9525.0;
|
||||||
|
}
|
||||||
|
double degrees = (double)rotation / 60000.0;
|
||||||
|
double radians = Math.toRadians(degrees);
|
||||||
|
double sin = Math.abs(Math.sin(radians));
|
||||||
|
double cos = Math.abs(Math.cos(radians));
|
||||||
|
double TOLERANCE = 1.0E-10;
|
||||||
|
if (sin < 1.0E-10) {
|
||||||
|
sin = 0.0;
|
||||||
|
}
|
||||||
|
if (cos < 1.0E-10) {
|
||||||
|
cos = 0.0;
|
||||||
|
}
|
||||||
|
int newWidth = (int)Math.ceil((double)origWidth * cos + (double)origHeight * sin);
|
||||||
|
int newHeight = (int)Math.ceil((double)origWidth * sin + (double)origHeight * cos);
|
||||||
|
newWidth = Math.max(1, newWidth);
|
||||||
|
newHeight = Math.max(1, newHeight);
|
||||||
|
int superSampleFactor = 4;
|
||||||
|
int ssWidth = origWidth * superSampleFactor;
|
||||||
|
int ssHeight = origHeight * superSampleFactor;
|
||||||
|
int ssNewW = newWidth * superSampleFactor;
|
||||||
|
int ssNewH = newHeight * superSampleFactor;
|
||||||
|
int imgType1 = originalImage.getType() == 0 ? 2 : originalImage.getType();
|
||||||
|
BufferedImage highResOriginal = new BufferedImage(ssWidth, ssHeight, imgType1);
|
||||||
|
Graphics2D g2dHighRes = highResOriginal.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dHighRes);
|
||||||
|
g2dHighRes.drawImage(originalImage, 0, 0, ssWidth, ssHeight, null);
|
||||||
|
g2dHighRes.dispose();
|
||||||
|
int imgType2 = highResOriginal.getType() == 0 ? 2 : highResOriginal.getType();
|
||||||
|
BufferedImage rotatedSuperSample = new BufferedImage(ssNewW, ssNewH, imgType2);
|
||||||
|
Graphics2D g2dRotated = rotatedSuperSample.createGraphics();
|
||||||
|
if (highResOriginal.getType() == 2) {
|
||||||
|
g2dRotated.setComposite(AlphaComposite.Clear);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
g2dRotated.setComposite(AlphaComposite.SrcOver);
|
||||||
|
} else {
|
||||||
|
g2dRotated.setColor(Color.WHITE);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
}
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2dRotated.translate((double)ssNewW / 2.0, (double)ssNewH / 2.0);
|
||||||
|
g2dRotated.rotate(radians);
|
||||||
|
g2dRotated.drawImage((Image)highResOriginal, -ssWidth / 2, -ssHeight / 2, null);
|
||||||
|
g2dRotated.dispose();
|
||||||
|
BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, rotatedSuperSample.getType() == 0 ? 2 : rotatedSuperSample.getType());
|
||||||
|
Graphics2D g2dFinal = rotatedImage.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dFinal);
|
||||||
|
g2dFinal.drawImage(rotatedSuperSample, 0, 0, newWidth, newHeight, null);
|
||||||
|
g2dFinal.dispose();
|
||||||
|
long newCx = Math.round((double)newWidth * emuPerPx);
|
||||||
|
long newCy = Math.round((double)newHeight * emuPerPx);
|
||||||
|
double newWidthPx = Math.round((double)newCx / 9525.0);
|
||||||
|
double newHeightPx = Math.round((double)newCy / 9525.0);
|
||||||
|
result.put(AttrEnum.WIDTH.getAttr(), newWidthPx);
|
||||||
|
result.put(AttrEnum.HEIGHT.getAttr(), newHeightPx);
|
||||||
|
return rotatedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureGraphics2D(Graphics2D g2d) {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseVmlCrop(String vmlCrop) {
|
||||||
|
if (vmlCrop == null || StringUtils.isBlank((CharSequence)vmlCrop)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String clean = vmlCrop.trim().replaceAll("[fF]$", "");
|
||||||
|
try {
|
||||||
|
int value = Integer.parseInt(clean);
|
||||||
|
return (int)Math.round((double)value * 1.52587890625);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
102
java-svc-jdk8/pom.xml
Normal file
102
java-svc-jdk8/pom.xml
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.wordparse</groupId>
|
||||||
|
<artifactId>word-file-parse-jdk8</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>WordFileParse JDK8</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Docx4J 3.3.7 (matches jby) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j</artifactId>
|
||||||
|
<version>3.3.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jsoup -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.17.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache Commons -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.14.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-collections4</artifactId>
|
||||||
|
<version>4.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.16.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- FastJSON -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>2.0.51</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>aliyun</id>
|
||||||
|
<url>https://maven.aliyun.com/repository/public</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<mainClass>com.wordparse.core.WordParseUtil</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<!-- Shade plugin for fat JAR -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.5.2</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals><goal>shade</goal></goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
79
java-svc-jdk8/src/main/java/com/wordparse/core/AttrEnum.java
Normal file
79
java-svc-jdk8/src/main/java/com/wordparse/core/AttrEnum.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
public enum AttrEnum {
|
||||||
|
STYLE("style"),
|
||||||
|
COLOR("color"),
|
||||||
|
BACKGROUND_COLOR("background-color"),
|
||||||
|
BACKGROUND("background"),
|
||||||
|
FONT_WEIGHT("font-weight"),
|
||||||
|
FONT_SIZE("font-size"),
|
||||||
|
MIN_HEIGHT("min-height"),
|
||||||
|
LINE_HEIGHT("line-height"),
|
||||||
|
TEXT_ALIGN("text-align"),
|
||||||
|
PADDING("padding"),
|
||||||
|
PADDING_TOP("padding-top"),
|
||||||
|
PADDING_RIGHT("padding-right"),
|
||||||
|
PADDING_BOTTOM("padding-bottom"),
|
||||||
|
PADDING_LEFT("padding-left"),
|
||||||
|
TEXT_INDENT("text-indent"),
|
||||||
|
MARGIN("margin"),
|
||||||
|
MARGIN_TOP("margin-top"),
|
||||||
|
MARGIN_RIGHT("margin-right"),
|
||||||
|
MARGIN_BOTTOM("margin-bottom"),
|
||||||
|
MARGIN_LEFT("margin-left"),
|
||||||
|
VERTICAL_ALIGN("vertical-align"),
|
||||||
|
FONT_FAMILY("font-family"),
|
||||||
|
FONT_STYLE("font-style"),
|
||||||
|
FACE("face"),
|
||||||
|
SIZE("size"),
|
||||||
|
HREF("href"),
|
||||||
|
SRC("src"),
|
||||||
|
WIDTH("width"),
|
||||||
|
HEIGHT("height"),
|
||||||
|
CLASS("class"),
|
||||||
|
BORDER_STYLE("border-style"),
|
||||||
|
COLSPAN("colspan"),
|
||||||
|
ROWSPAN("rowspan"),
|
||||||
|
TEXT_DECORATION("text-decoration"),
|
||||||
|
BACKGROUND_IMAGE("background-image"),
|
||||||
|
TINYMCE_HORIZONTAL_LINE("tinymce-horizontal-line"),
|
||||||
|
TINYMCE_LINE_NUMBER("tinymce-line-number"),
|
||||||
|
TEXT_EMPHASIS_POSITION("text-emphasis-position"),
|
||||||
|
DATA_TYPE("data-type"),
|
||||||
|
SEGMENT("segment"),
|
||||||
|
BUSINESS("business"),
|
||||||
|
LIST_STYLE_TYPE("list-style-type"),
|
||||||
|
ALT("alt"),
|
||||||
|
NAME("name"),
|
||||||
|
DATA_HREF("data-href"),
|
||||||
|
DATA_File_SUFFIX("data-file-suffix"),
|
||||||
|
TINYMCE_LINK("tinymce-link"),
|
||||||
|
DATA_RESOURCE_URL("data-resource-url"),
|
||||||
|
DATA_COURSE_NETWORK_LESSON_RESOURCE_NAME("data-course-network-lesson-resource-name"),
|
||||||
|
TINYMCE_HOLLOWOUT("tinymce-hollowOut"),
|
||||||
|
TINYMCE_HIDDENONLY("tinymce-hiddenOnly"),
|
||||||
|
TINYMCE_HIDDENANDANSWER("tinymce-hiddenAndAnswer"),
|
||||||
|
BORDER("border"),
|
||||||
|
BORDER_LEFT("border-left"),
|
||||||
|
BORDER_BOTTOM("border-bottom"),
|
||||||
|
BORDER_RIGHT("border-right"),
|
||||||
|
BORDER_TOP("border-top"),
|
||||||
|
BORDER_LEFT_COLOR("border-left-color"),
|
||||||
|
BORDER_BOTTOM_COLOR("border-bottom-color"),
|
||||||
|
BORDER_RIGHT_COLOR("border-right-color"),
|
||||||
|
BORDER_TOP_COLOR("border-top-color");
|
||||||
|
|
||||||
|
private final String attr;
|
||||||
|
|
||||||
|
public String getAttr() {
|
||||||
|
return this.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttrEnum(String attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
|
||||||
|
public class CtObjectFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtObjectFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof CTOLEObject) {
|
||||||
|
CTOLEObject ctoleObject = (CTOLEObject)o;
|
||||||
|
RelationshipsPart rsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
if (Objects.nonNull(ctoleObject.getId())) {
|
||||||
|
Part part = rsPart.getPart(ctoleObject.getId());
|
||||||
|
String binFilePath = part.getPartName().getURI().getPath();
|
||||||
|
String placeholder = this.parseBO.getMathTypePlaceholder(ctoleObject.getId());
|
||||||
|
this.parseBO.getMathTypeMap().put(placeholder, this.parseBO.getTempDir() + "/temp" + binFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TextUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.mce.AlternateContent
|
||||||
|
* org.docx4j.mce.AlternateContent$Fallback
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.vml.CTGroup
|
||||||
|
* org.docx4j.vml.CTRoundRect
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.CTTextbox
|
||||||
|
* org.docx4j.vml.VmlShapeElements
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TextUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.mce.AlternateContent;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.vml.CTGroup;
|
||||||
|
import org.docx4j.vml.CTRoundRect;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.CTTextbox;
|
||||||
|
import org.docx4j.vml.VmlShapeElements;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CtTextBoxFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtTextBoxFinder.class);
|
||||||
|
private WordprocessingMLPackage wordMlPackage;
|
||||||
|
private ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtTextBoxFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof Pict) {
|
||||||
|
boolean isFirstRun = true;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
R sourceR = factory.createR();
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
List anyAndAny = pict.getAnyAndAny();
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
if (!(object instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement jbe = (JAXBElement)object;
|
||||||
|
this.findTextBox(jbe.getValue(), stringBuilder, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)stringBuilder.toString())) {
|
||||||
|
AlternateContent.Fallback fallback = (AlternateContent.Fallback)pict.getParent();
|
||||||
|
AlternateContent alternateContent = (AlternateContent)fallback.getParent();
|
||||||
|
R r = (R)alternateContent.getParent();
|
||||||
|
Text text = factory.createText();
|
||||||
|
text.setValue(stringBuilder + "JBY_TEXTBOX_JBY");
|
||||||
|
r.getContent().set(0, text);
|
||||||
|
r.setRPr(sourceR.getRPr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findTextBox(Object object, StringBuilder sb, boolean isFirstRun, R sourceR) {
|
||||||
|
block8: {
|
||||||
|
List egShapeElements;
|
||||||
|
if (object instanceof CTShape || object instanceof CTRoundRect) {
|
||||||
|
VmlShapeElements vmlShapeElements = (VmlShapeElements)object;
|
||||||
|
egShapeElements = vmlShapeElements.getEGShapeElements();
|
||||||
|
for (JAXBElement egShapeElement : egShapeElements) {
|
||||||
|
if (!(egShapeElement.getValue() instanceof CTTextbox)) continue;
|
||||||
|
CTTextbox ctTextbox = (CTTextbox)egShapeElement.getValue();
|
||||||
|
CTTxbxContent txbxContent = ctTextbox.getTxbxContent();
|
||||||
|
for (int i = 0; i < txbxContent.getContent().size(); ++i) {
|
||||||
|
Object obj = txbxContent.getContent().get(i);
|
||||||
|
String text = TextUtils.getText(obj);
|
||||||
|
if (txbxContent.getContent().size() > 1 && i != txbxContent.getContent().size() - 1) {
|
||||||
|
text = text + "JBY_TEXTBOX_JBYBR";
|
||||||
|
}
|
||||||
|
sb.append(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(object instanceof CTGroup)) break block8;
|
||||||
|
CTGroup ctGroup = (CTGroup)object;
|
||||||
|
egShapeElements = ctGroup.getEGShapeElements();
|
||||||
|
boolean isCtShape = egShapeElements.stream().anyMatch(f -> f.getValue() instanceof CTShape);
|
||||||
|
if (isCtShape) {
|
||||||
|
for (int i = egShapeElements.size() - 1; i >= 0; --i) {
|
||||||
|
this.findTextBox(((JAXBElement)egShapeElements.get(i)).getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (JAXBElement egShapeElement : egShapeElements) {
|
||||||
|
this.findTextBox(egShapeElement.getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,427 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.XmlUtils
|
||||||
|
* org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTTextboxInfo
|
||||||
|
* org.docx4j.math.CTOMath
|
||||||
|
* org.docx4j.math.CTOMathPara
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.CTVerticalAlignRun
|
||||||
|
* org.docx4j.wml.FldChar
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.P
|
||||||
|
* org.docx4j.wml.PPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr$Ilvl
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.RPrAbstract
|
||||||
|
* org.docx4j.wml.STFldCharType
|
||||||
|
* org.docx4j.wml.Style
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.docx4j.wml.U
|
||||||
|
* org.docx4j.wml.UnderlineEnumeration
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import com.wordparse.core.EqToLatexUtil;
|
||||||
|
import com.wordparse.core.NumPr;
|
||||||
|
import com.wordparse.core.WordParseUtil;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.XmlUtils;
|
||||||
|
import org.docx4j.com.microsoft.schemas.office.word.x2010.wordprocessingShape.CTTextboxInfo;
|
||||||
|
import org.docx4j.math.CTOMath;
|
||||||
|
import org.docx4j.math.CTOMathPara;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.CTVerticalAlignRun;
|
||||||
|
import org.docx4j.wml.FldChar;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.P;
|
||||||
|
import org.docx4j.wml.PPr;
|
||||||
|
import org.docx4j.wml.PPrBase;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.RPrAbstract;
|
||||||
|
import org.docx4j.wml.STFldCharType;
|
||||||
|
import org.docx4j.wml.Style;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.docx4j.wml.U;
|
||||||
|
import org.docx4j.wml.UnderlineEnumeration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CtoMathFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtoMathFinder.class);
|
||||||
|
private ParseBO parseBO;
|
||||||
|
private static final Pattern CIRCLED_NUMBER_PATTERN = Pattern.compile("^\\s*eq\\s*\\\\o\\((?:\u25cb,\\\\s\\\\up\\d+\\((\\d+)\\)|\\\\s\\\\up\\d+\\((\\d+)\\),\u25cb)\\)");
|
||||||
|
|
||||||
|
public CtoMathFinder(ParseBO parseBO) {
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof P) {
|
||||||
|
Text text;
|
||||||
|
Style style;
|
||||||
|
CTTxbxContent ctTxbxContent;
|
||||||
|
StringBuilder eqCode = new StringBuilder();
|
||||||
|
StringBuilder originEqCode = new StringBuilder();
|
||||||
|
boolean inFiled = false;
|
||||||
|
boolean isEq = false;
|
||||||
|
boolean superScriptFlag = false;
|
||||||
|
boolean subScriptFlag = false;
|
||||||
|
ArrayList<Integer> fieldRunIndexs = new ArrayList<Integer>();
|
||||||
|
P paragraph = (P)o;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
boolean isNumPr = true;
|
||||||
|
if (paragraph.getParent() instanceof CTTxbxContent && (ctTxbxContent = (CTTxbxContent)paragraph.getParent()).getParent() instanceof CTTextboxInfo) {
|
||||||
|
isNumPr = false;
|
||||||
|
}
|
||||||
|
PPr pPr = paragraph.getPPr();
|
||||||
|
PPrBase.NumPr numPr = null;
|
||||||
|
if (Objects.nonNull(pPr)) {
|
||||||
|
numPr = pPr.getNumPr();
|
||||||
|
}
|
||||||
|
if (Objects.isNull(numPr) && Objects.nonNull(pPr) && Objects.nonNull(pPr.getPStyle()) && Objects.nonNull(style = this.parseBO.getStyleDefinitionsPart().getStyleById(pPr.getPStyle().getVal())) && Objects.nonNull(style.getPPr()) && Objects.nonNull(style.getPPr().getNumPr()) && Objects.isNull((numPr = style.getPPr().getNumPr()).getIlvl())) {
|
||||||
|
PPrBase.NumPr.Ilvl ilvl = new PPrBase.NumPr.Ilvl();
|
||||||
|
ilvl.setVal(BigInteger.ZERO);
|
||||||
|
numPr.setIlvl(ilvl);
|
||||||
|
}
|
||||||
|
if (isNumPr && Objects.nonNull(numPr) && Objects.nonNull(numPr.getNumId()) && Objects.nonNull(numPr.getIlvl())) {
|
||||||
|
String numPrKey = numPr.getNumId().getVal() + "_" + numPr.getIlvl().getVal();
|
||||||
|
Map<String, NumPr> numberingDefinitionsMap = this.parseBO.getNumberingDefinitionsMap();
|
||||||
|
if (numberingDefinitionsMap.containsKey(numPrKey)) {
|
||||||
|
NumPr currentNumPr = numberingDefinitionsMap.get(numPrKey);
|
||||||
|
String lvlText = currentNumPr.getLvlText();
|
||||||
|
for (int i = 0; i <= numPr.getIlvl().getVal().intValue(); ++i) {
|
||||||
|
NumPr numPrDefine;
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
currentNumPr.setCurrent(currentNumPr.getCurrent().add(BigInteger.ONE));
|
||||||
|
}
|
||||||
|
String rep = String.valueOf((numPrDefine = numberingDefinitionsMap.get(numPr.getNumId().getVal() + "_" + i)).getCurrent().compareTo(BigInteger.ZERO) == 0 ? numPrDefine.getStart() : numPrDefine.getCurrent());
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
switch (currentNumPr.getNumFmt()) {
|
||||||
|
case "UPPER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DECIMAL_ZERO": {
|
||||||
|
rep = CtoMathFinder.strToDecimalZero(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPPER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep).toUpperCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep).toLowerCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CHINESE_COUNTING":
|
||||||
|
case "CHINESE_COUNTING_THOUSAND": {
|
||||||
|
rep = this.strToChineseCounting(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lvlText = lvlText.replace("%" + (i + 1), rep);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)lvlText)) {
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(lvlText + " ");
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().add(0, run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < paragraph.getContent().size(); ++i) {
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
Object content = paragraph.getContent().get(i);
|
||||||
|
if (content instanceof JAXBElement && ((jaxbElement = (JAXBElement)content).getValue() instanceof CTOMath || jaxbElement.getValue() instanceof CTOMathPara)) {
|
||||||
|
String placeholder = "";
|
||||||
|
if (Boolean.TRUE.equals(this.parseBO.getNewParseVersion())) {
|
||||||
|
String omml = XmlUtils.marshaltoString((Object)jaxbElement);
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, omml);
|
||||||
|
} else {
|
||||||
|
String mathMl = WordParseUtil.getMathMl(jaxbElement, this.parseBO.getTransformer());
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, mathMl);
|
||||||
|
}
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(placeholder);
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().set(i, run);
|
||||||
|
}
|
||||||
|
if (!(content instanceof R)) continue;
|
||||||
|
R run = (R)content;
|
||||||
|
for (int j = 0; j < run.getContent().size(); ++j) {
|
||||||
|
Object runC = run.getContent().get(j);
|
||||||
|
if (!(runC instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement runCj = (JAXBElement)runC;
|
||||||
|
if (runCj.getValue() instanceof FldChar) {
|
||||||
|
FldChar fldChar = (FldChar)runCj.getValue();
|
||||||
|
if (fldChar.getFldCharType().equals((Object)STFldCharType.BEGIN)) {
|
||||||
|
eqCode = new StringBuilder();
|
||||||
|
originEqCode = new StringBuilder();
|
||||||
|
inFiled = true;
|
||||||
|
isEq = false;
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
fieldRunIndexs = new ArrayList();
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && fldChar.getFldCharType().equals((Object)STFldCharType.END)) {
|
||||||
|
if (isEq) {
|
||||||
|
String placeholder;
|
||||||
|
Matcher matcher = CIRCLED_NUMBER_PATTERN.matcher(originEqCode);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String numStr = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder() + "JBYEQNUM";
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, CtoMathFinder.getCircledNumber(numStr));
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text2 = factory.createText();
|
||||||
|
text2.setValue(placeholder);
|
||||||
|
runN.getContent().add(text2);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)eqCode.toString())) {
|
||||||
|
String latex = EqToLatexUtil.convert(eqCode.toString());
|
||||||
|
latex = latex.replaceAll("<", "<").replaceAll(">", ">");
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder();
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, latex);
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text3 = factory.createText();
|
||||||
|
text3.setValue(placeholder);
|
||||||
|
runN.getContent().add(text3);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
isEq = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && isEq && !fldChar.getFldCharType().value().equals("separate")) continue;
|
||||||
|
}
|
||||||
|
if (!inFiled || !(runCj.getValue() instanceof Text)) continue;
|
||||||
|
CTVerticalAlignRun vertAlign = Optional.ofNullable(run.getRPr()).map(RPrAbstract::getVertAlign).orElse(null);
|
||||||
|
String value = ((Text)runCj.getValue()).getValue();
|
||||||
|
originEqCode.append(value);
|
||||||
|
U u = Optional.ofNullable(run.getRPr()).filter(f -> Objects.nonNull(f.getU()) && !UnderlineEnumeration.NONE.equals((Object)f.getU().getVal()) && Objects.nonNull(f.getU().getVal())).map(RPrAbstract::getU).orElse(null);
|
||||||
|
if (StringUtils.isBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = value.replace("\u3000", "JBYU1JBYU").replace(" ", "JBYU1JBYU");
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = "JBYU2BEGIN" + value + "JBYU2END";
|
||||||
|
} else if (StringUtils.isBlank((CharSequence)value)) {
|
||||||
|
// empty if block
|
||||||
|
}
|
||||||
|
String trimmedOrigin = CtoMathFinder.extractEqPrefix(originEqCode);
|
||||||
|
if (trimmedOrigin.startsWith("eq")) {
|
||||||
|
isEq = true;
|
||||||
|
}
|
||||||
|
if (isEq) {
|
||||||
|
if (CollectionUtils.isNotEmpty(fieldRunIndexs)) {
|
||||||
|
fieldRunIndexs.forEach(m -> paragraph.getContent().set((int)m, factory.createR()));
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(vertAlign)) {
|
||||||
|
String newContent;
|
||||||
|
String tmp;
|
||||||
|
if ("superscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (superScriptFlag && (start = eqCode.lastIndexOf("\\s\\up4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\up4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\up4(" + value + ")";
|
||||||
|
}
|
||||||
|
superScriptFlag = true;
|
||||||
|
subScriptFlag = false;
|
||||||
|
} else if ("subscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (subScriptFlag && (start = eqCode.lastIndexOf("\\s\\do4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\do4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\do4(" + value + ")";
|
||||||
|
}
|
||||||
|
subScriptFlag = true;
|
||||||
|
superScriptFlag = false;
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
eqCode.append(value);
|
||||||
|
paragraph.getContent().set(i, factory.createR());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (trimmedOrigin.length() < 2 || "eq".startsWith(trimmedOrigin)) {
|
||||||
|
eqCode.append(value);
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String strToUpperRoman(String str) {
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 1 || num > 3999) {
|
||||||
|
return "I";
|
||||||
|
}
|
||||||
|
int[] values = new int[]{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
|
||||||
|
String[] symbols = new String[]{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
|
||||||
|
StringBuilder roman = new StringBuilder();
|
||||||
|
for (int i = 0; i < values.length; ++i) {
|
||||||
|
while (num >= values[i]) {
|
||||||
|
roman.append(symbols[i]);
|
||||||
|
num -= values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roman.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToDecimalZero(String str) {
|
||||||
|
if (str.length() == 1) {
|
||||||
|
return "0" + str;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToLowerLetter(String str) {
|
||||||
|
int n = Integer.parseInt(str);
|
||||||
|
int length = (n - 1) / 26 + 1;
|
||||||
|
char baseChar = (char)(97 + (n - 1) % 26);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
sb.append(baseChar);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String strToChineseCounting(String str) {
|
||||||
|
String s;
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 0 || num >= 1000000000) {
|
||||||
|
throw new IllegalArgumentException("\u4ec5\u652f\u6301 0 \u5230 999,999,999");
|
||||||
|
}
|
||||||
|
if (num == 0) {
|
||||||
|
return "\u3007";
|
||||||
|
}
|
||||||
|
char[] digits = new char[]{'\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d'};
|
||||||
|
String[] units = new String[]{"", "\u5341", "\u767e", "\u5343"};
|
||||||
|
String[] bigUnits = new String[]{"", "\u4e07", "\u4ebf"};
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int yi = num / 100000000;
|
||||||
|
int wan = num / 10000 % 10000;
|
||||||
|
int ge = num % 10000;
|
||||||
|
if (yi > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(yi, digits, units)).append(bigUnits[2]);
|
||||||
|
if (wan == 0 && ge > 0) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wan > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(wan, digits, units)).append(bigUnits[1]);
|
||||||
|
if (ge > 0 && ge < 1000) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ge > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(ge, digits, units));
|
||||||
|
}
|
||||||
|
if ((s = result.toString()).startsWith("\u4e00\u5341")) {
|
||||||
|
s = s.substring(1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertSegment(int n, char[] digits, String[] units) {
|
||||||
|
if (n == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int power = 3 - i;
|
||||||
|
int base = (int)Math.pow(10.0, power);
|
||||||
|
int digit = n / base % 10;
|
||||||
|
if (digit != 0) {
|
||||||
|
sb.append(digits[digit]).append(units[power]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sb.length() <= 0 || n % base == 0 || power <= 0) continue;
|
||||||
|
sb.append('\u3007');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCircledNumber(String num) {
|
||||||
|
int n = Integer.parseInt(num);
|
||||||
|
if (n >= 1 && n <= 20) {
|
||||||
|
return String.valueOf((char)(9312 + n - 1));
|
||||||
|
}
|
||||||
|
if (n >= 21 && n <= 35) {
|
||||||
|
return String.valueOf((char)(12881 + n - 21));
|
||||||
|
}
|
||||||
|
if (n >= 36 && n <= 40) {
|
||||||
|
return String.valueOf((char)(12977 + n - 36));
|
||||||
|
}
|
||||||
|
if (n >= 41 && n <= 50) {
|
||||||
|
return String.valueOf((char)(12982 + n - 41));
|
||||||
|
}
|
||||||
|
log.info("\u83b7\u53d6\u5e26\u5708\u5b57\u7b26\u5931\u8d25 {}", (Object)num);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractEqPrefix(StringBuilder originEqCode) {
|
||||||
|
int i;
|
||||||
|
String s = originEqCode.toString().trim();
|
||||||
|
for (i = 0; i < s.length() && s.charAt(i) == '{'; ++i) {
|
||||||
|
}
|
||||||
|
return s.substring(i).trim().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.excel.util.StringUtils
|
||||||
|
* com.wordparse.core.UniqueUtil
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.dml.GraphicData
|
||||||
|
* org.docx4j.dml.picture.Pic
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Anchor
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Inline
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.CTImageData
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
* org.docx4j.wml.CTObject
|
||||||
|
* org.docx4j.wml.Drawing
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.alibaba.excel.util.StringUtils;
|
||||||
|
import com.wordparse.core.UniqueUtil;
|
||||||
|
import com.wordparse.core.ImageBO;
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import com.wordparse.core.AttrEnum;
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.dml.GraphicData;
|
||||||
|
import org.docx4j.dml.picture.Pic;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Anchor;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Inline;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.CTImageData;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
import org.docx4j.wml.CTObject;
|
||||||
|
import org.docx4j.wml.Drawing;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DrawingPictFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DrawingPictFinder.class);
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
private static final double CROP_SCALE = 1.52587890625;
|
||||||
|
|
||||||
|
public DrawingPictFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
List contentList;
|
||||||
|
List anyAndAny;
|
||||||
|
R r;
|
||||||
|
if (o instanceof Drawing && ((Drawing)o).getParent() instanceof R) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Drawing drawing = (Drawing)o;
|
||||||
|
List anchorOrInline = drawing.getAnchorOrInline();
|
||||||
|
if (CollectionUtils.isNotEmpty((Collection)anchorOrInline)) {
|
||||||
|
Object anchorOrInlineObj = anchorOrInline.get(0);
|
||||||
|
GraphicData graphicData = null;
|
||||||
|
long cx = 0L;
|
||||||
|
long cy = 0L;
|
||||||
|
if (anchorOrInlineObj instanceof Inline) {
|
||||||
|
Inline inline = (Inline)anchorOrInlineObj;
|
||||||
|
graphicData = inline.getGraphic().getGraphicData();
|
||||||
|
cx = inline.getExtent().getCx();
|
||||||
|
cy = inline.getExtent().getCy();
|
||||||
|
} else if (anchorOrInlineObj instanceof Anchor) {
|
||||||
|
Anchor anchor = (Anchor)anchorOrInlineObj;
|
||||||
|
graphicData = anchor.getGraphic().getGraphicData();
|
||||||
|
cx = anchor.getExtent().getCx();
|
||||||
|
cy = anchor.getExtent().getCy();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(graphicData) && "http://schemas.openxmlformats.org/drawingml/2006/picture".equals(graphicData.getUri())) {
|
||||||
|
for (Object object : graphicData.getAny()) {
|
||||||
|
String path;
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
if (!(object instanceof JAXBElement) || !((jaxbElement = (JAXBElement)object).getValue() instanceof Pic)) continue;
|
||||||
|
Pic pic = (Pic)jaxbElement.getValue();
|
||||||
|
String imageDataId = null;
|
||||||
|
int cropLeft = 0;
|
||||||
|
int cropTop = 0;
|
||||||
|
int cropRight = 0;
|
||||||
|
int cropBottom = 0;
|
||||||
|
int rotation = 0;
|
||||||
|
if (Objects.nonNull(pic.getBlipFill())) {
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getBlip())) {
|
||||||
|
imageDataId = pic.getBlipFill().getBlip().getEmbed();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getSrcRect())) {
|
||||||
|
cropLeft = pic.getBlipFill().getSrcRect().getL();
|
||||||
|
cropTop = pic.getBlipFill().getSrcRect().getT();
|
||||||
|
cropRight = pic.getBlipFill().getSrcRect().getR();
|
||||||
|
cropBottom = pic.getBlipFill().getSrcRect().getB();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getSpPr()) && Objects.nonNull(pic.getSpPr().getXfrm())) {
|
||||||
|
rotation = pic.getSpPr().getXfrm().getRot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Object> map = this.dealCropRotate(imageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
Object heightObj = map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
double width = Objects.nonNull(widthObj) ? Double.parseDouble(widthObj.toString()) : (double)Math.round((double)cx / 9525.0);
|
||||||
|
double height = Objects.nonNull(heightObj) ? Double.parseDouble(heightObj.toString()) : (double)Math.round((double)cy / 9525.0);
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u56fe\u7247Part\uff0cimageDataId={}", (Object)imageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(imageDataId);
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(width).setHeight(height).setPictureUrl(path);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
R r2 = (R)drawing.getParent();
|
||||||
|
List contentList2 = r2.getContent();
|
||||||
|
int drawingIndex = contentList2.indexOf(drawing);
|
||||||
|
if (drawingIndex != -1) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
contentList2.set(drawingIndex, text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.warn("\u672a\u627e\u5230drawing\u5bf9\u8c61\u5728\u7236\u5143\u7d20\u4e2d\u7684\u7d22\u5f15\uff0c\u8df3\u8fc7\u66ff\u6362\u64cd\u4f5c");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof Pict && ((Pict)o).getParent() instanceof R) {
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
r = (R)pict.getParent();
|
||||||
|
anyAndAny = pict.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int pictIndex = contentList.indexOf(pict);
|
||||||
|
if (pictIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, pictIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof CTObject && ((CTObject)o).getParent() instanceof R) {
|
||||||
|
CTObject ctObject = (CTObject)o;
|
||||||
|
r = (R)ctObject.getParent();
|
||||||
|
anyAndAny = ctObject.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int ctObjectIndex = contentList.indexOf(ctObject);
|
||||||
|
if (ctObjectIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, ctObjectIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractValue(String style, String property) {
|
||||||
|
if (style == null || property == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String regex = "\\b" + Pattern.quote(property) + "\\s*:\\s*([\\d.]+[a-zA-Z]*)";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(style);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dealCtShape(List<Object> anyAndAny, R r, int pictIndex) {
|
||||||
|
String ctOleObjectRid = anyAndAny.stream().filter(f -> f instanceof JAXBElement && ((JAXBElement)f).getValue() instanceof CTOLEObject).map(f -> ((CTOLEObject)((JAXBElement)f).getValue()).getId()).filter(Objects::nonNull).findFirst().orElse(null);
|
||||||
|
if (Objects.nonNull(ctOleObjectRid) && Objects.nonNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
} else if (Objects.isNull(ctOleObjectRid) || Objects.isNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
if (Objects.nonNull(ctOleObjectRid)) {
|
||||||
|
this.parseBO.getMathTypeMap().remove(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
}
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
String path;
|
||||||
|
double heightPx;
|
||||||
|
double widthPx;
|
||||||
|
if (!(object instanceof JAXBElement) || !(((JAXBElement)object).getValue() instanceof CTShape)) continue;
|
||||||
|
CTShape ctShape = (CTShape)((JAXBElement)object).getValue();
|
||||||
|
String width = this.extractValue(ctShape.getStyle(), "width");
|
||||||
|
String height = this.extractValue(ctShape.getStyle(), "height");
|
||||||
|
CTImageData ctImageData = ctShape.getEGShapeElements().stream().filter(f -> f.getValue() instanceof CTImageData).map(m -> (CTImageData)m.getValue()).findFirst().orElse(null);
|
||||||
|
if (!Objects.nonNull(ctImageData) || !StringUtils.isNotBlank((CharSequence)width) || !StringUtils.isNotBlank((CharSequence)height)) continue;
|
||||||
|
String ctImageDataId = ctImageData.getId();
|
||||||
|
long cx = 0L;
|
||||||
|
try {
|
||||||
|
double widthPtIn = Double.parseDouble(width.substring(0, width.length() - 2));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
cx = Math.round(widthPtIn * 12700.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
cx = Math.round(widthPtIn * 914400.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ignored) {
|
||||||
|
log.error("extractWidth error");
|
||||||
|
}
|
||||||
|
int cropLeft = this.parseVmlCrop(ctImageData.getCropleft());
|
||||||
|
int cropTop = this.parseVmlCrop(ctImageData.getCroptop());
|
||||||
|
int cropRight = this.parseVmlCrop(ctImageData.getCropright());
|
||||||
|
int cropBottom = this.parseVmlCrop(ctImageData.getCropbottom());
|
||||||
|
String rotRaw = this.extractValue(ctShape.getStyle(), "rotation");
|
||||||
|
int rotation = rotRaw == null ? 0 : (int)Math.round((double)Long.parseLong(rotRaw.replaceAll("[^\\d-]", "")) * 0.91552734375);
|
||||||
|
Map<String, Object> map = this.dealCropRotate(ctImageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
Object heightObj = map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
if (Objects.nonNull(widthObj) && Objects.nonNull(heightObj)) {
|
||||||
|
widthPx = (Double)map.get(AttrEnum.WIDTH.getAttr());
|
||||||
|
heightPx = (Double)map.get(AttrEnum.HEIGHT.getAttr());
|
||||||
|
} else {
|
||||||
|
widthPx = Double.parseDouble(width.replaceAll("pt|in", ""));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0 / 72.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0);
|
||||||
|
}
|
||||||
|
heightPx = Double.parseDouble(height.replaceAll("pt|in", ""));
|
||||||
|
if (height.endsWith("pt")) {
|
||||||
|
heightPx = heightPx * 96.0 / 72.0;
|
||||||
|
} else if (height.endsWith("in")) {
|
||||||
|
heightPx *= 96.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(ctImageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u8001\u7248VML\u56fe\u7247Part\uff0cctImageDataId={}", (Object)ctImageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(widthPx).setHeight(heightPx).setPictureUrl(path);
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(ctImageDataId);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> dealCropRotate(String imageDataId, long cx, int cropLeft, int cropTop, int cropRight, int cropBottom, int rotation) {
|
||||||
|
HashMap<String, Object> result = new HashMap<String, Object>();
|
||||||
|
try {
|
||||||
|
if (Objects.nonNull(imageDataId)) {
|
||||||
|
boolean isRotate;
|
||||||
|
boolean isCrop = cropLeft > 0 || cropTop > 0 || cropRight > 0 || cropBottom > 0;
|
||||||
|
boolean bl = isRotate = rotation != 0;
|
||||||
|
if (isCrop || isRotate) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage)part;
|
||||||
|
BufferedImage targetImage = ImageIO.read(new ByteArrayInputStream(imagePart.getBytes()));
|
||||||
|
if (isCrop) {
|
||||||
|
targetImage = this.cropImage(targetImage, cropLeft, cropTop, cropRight, cropBottom);
|
||||||
|
}
|
||||||
|
if (isRotate) {
|
||||||
|
targetImage = this.rotateImage(targetImage, rotation, cx, result);
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(targetImage)) {
|
||||||
|
String formatName = this.getFormatName(imagePart);
|
||||||
|
String path = "/word/media/" + UniqueUtil.getUuid() + "." + formatName;
|
||||||
|
ImageIO.write((RenderedImage)targetImage, formatName, new File(this.parseBO.getTempDir() + "/temp" + path));
|
||||||
|
result.put("imagePath", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5904\u7406\u56fe\u7247\u88c1\u526a\u4e0e\u65cb\u8f6c\u5f02\u5e38", (Throwable)e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFormatName(BinaryPartAbstractImage imagePart) {
|
||||||
|
String contentType = imagePart.getContentType();
|
||||||
|
String formatName = contentType.contains("png") ? "png" : (contentType.contains("jpeg") ? "jpeg" : (contentType.contains("gif") ? "gif" : (contentType.contains("bmp") ? "bmp" : "png")));
|
||||||
|
return formatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage cropImage(BufferedImage originalImage, int srcRectL, int srcRectT, int srcRectR, int srcRectB) {
|
||||||
|
double leftRatio = srcRectL < 0 ? 0.0 : (double)srcRectL / 100000.0;
|
||||||
|
double topRatio = srcRectT < 0 ? 0.0 : (double)srcRectT / 100000.0;
|
||||||
|
double rightRatio = srcRectR < 0 ? 0.0 : (double)srcRectR / 100000.0;
|
||||||
|
double bottomRatio = srcRectB < 0 ? 0.0 : (double)srcRectB / 100000.0;
|
||||||
|
int originalWidth = originalImage.getWidth();
|
||||||
|
int originalHeight = originalImage.getHeight();
|
||||||
|
int left = (int)Math.floor(leftRatio * (double)originalWidth);
|
||||||
|
int top = (int)Math.floor(topRatio * (double)originalHeight);
|
||||||
|
int right = (int)Math.floor(rightRatio * (double)originalWidth);
|
||||||
|
int bottom = (int)Math.floor(bottomRatio * (double)originalHeight);
|
||||||
|
int cropWidth = Math.max(1, originalWidth - left - right);
|
||||||
|
int cropHeight = Math.max(1, originalHeight - top - bottom);
|
||||||
|
if (left < 0 || top < 0 || left + cropWidth > originalWidth || top + cropHeight > originalHeight) {
|
||||||
|
log.error("\u88c1\u526a\u533a\u57df\u65e0\u6548\u6216\u8d85\u51fa\u56fe\u7247\u8fb9\u754c");
|
||||||
|
}
|
||||||
|
return originalImage.getSubimage(left, top, cropWidth, cropHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage rotateImage(BufferedImage originalImage, long rotation, long currentCx, Map<String, Object> result) {
|
||||||
|
int origWidth = originalImage.getWidth();
|
||||||
|
int origHeight = originalImage.getHeight();
|
||||||
|
if (origWidth <= 0 || origHeight <= 0) {
|
||||||
|
log.error("\u56fe\u7247\u533a\u57df\u65e0\u6548");
|
||||||
|
}
|
||||||
|
double emuPerPx = (double)currentCx / (double)origWidth;
|
||||||
|
if (currentCx == 0L) {
|
||||||
|
emuPerPx = 9525.0;
|
||||||
|
}
|
||||||
|
double degrees = (double)rotation / 60000.0;
|
||||||
|
double radians = Math.toRadians(degrees);
|
||||||
|
double sin = Math.abs(Math.sin(radians));
|
||||||
|
double cos = Math.abs(Math.cos(radians));
|
||||||
|
double TOLERANCE = 1.0E-10;
|
||||||
|
if (sin < 1.0E-10) {
|
||||||
|
sin = 0.0;
|
||||||
|
}
|
||||||
|
if (cos < 1.0E-10) {
|
||||||
|
cos = 0.0;
|
||||||
|
}
|
||||||
|
int newWidth = (int)Math.ceil((double)origWidth * cos + (double)origHeight * sin);
|
||||||
|
int newHeight = (int)Math.ceil((double)origWidth * sin + (double)origHeight * cos);
|
||||||
|
newWidth = Math.max(1, newWidth);
|
||||||
|
newHeight = Math.max(1, newHeight);
|
||||||
|
int superSampleFactor = 4;
|
||||||
|
int ssWidth = origWidth * superSampleFactor;
|
||||||
|
int ssHeight = origHeight * superSampleFactor;
|
||||||
|
int ssNewW = newWidth * superSampleFactor;
|
||||||
|
int ssNewH = newHeight * superSampleFactor;
|
||||||
|
int imgType1 = originalImage.getType() == 0 ? 2 : originalImage.getType();
|
||||||
|
BufferedImage highResOriginal = new BufferedImage(ssWidth, ssHeight, imgType1);
|
||||||
|
Graphics2D g2dHighRes = highResOriginal.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dHighRes);
|
||||||
|
g2dHighRes.drawImage(originalImage, 0, 0, ssWidth, ssHeight, null);
|
||||||
|
g2dHighRes.dispose();
|
||||||
|
int imgType2 = highResOriginal.getType() == 0 ? 2 : highResOriginal.getType();
|
||||||
|
BufferedImage rotatedSuperSample = new BufferedImage(ssNewW, ssNewH, imgType2);
|
||||||
|
Graphics2D g2dRotated = rotatedSuperSample.createGraphics();
|
||||||
|
if (highResOriginal.getType() == 2) {
|
||||||
|
g2dRotated.setComposite(AlphaComposite.Clear);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
g2dRotated.setComposite(AlphaComposite.SrcOver);
|
||||||
|
} else {
|
||||||
|
g2dRotated.setColor(Color.WHITE);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
}
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2dRotated.translate((double)ssNewW / 2.0, (double)ssNewH / 2.0);
|
||||||
|
g2dRotated.rotate(radians);
|
||||||
|
g2dRotated.drawImage((Image)highResOriginal, -ssWidth / 2, -ssHeight / 2, null);
|
||||||
|
g2dRotated.dispose();
|
||||||
|
BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, rotatedSuperSample.getType() == 0 ? 2 : rotatedSuperSample.getType());
|
||||||
|
Graphics2D g2dFinal = rotatedImage.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dFinal);
|
||||||
|
g2dFinal.drawImage(rotatedSuperSample, 0, 0, newWidth, newHeight, null);
|
||||||
|
g2dFinal.dispose();
|
||||||
|
long newCx = Math.round((double)newWidth * emuPerPx);
|
||||||
|
long newCy = Math.round((double)newHeight * emuPerPx);
|
||||||
|
double newWidthPx = Math.round((double)newCx / 9525.0);
|
||||||
|
double newHeightPx = Math.round((double)newCy / 9525.0);
|
||||||
|
result.put(AttrEnum.WIDTH.getAttr(), newWidthPx);
|
||||||
|
result.put(AttrEnum.HEIGHT.getAttr(), newHeightPx);
|
||||||
|
return rotatedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureGraphics2D(Graphics2D g2d) {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseVmlCrop(String vmlCrop) {
|
||||||
|
if (vmlCrop == null || StringUtils.isBlank((CharSequence)vmlCrop)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String clean = vmlCrop.trim().replaceAll("[fF]$", "");
|
||||||
|
try {
|
||||||
|
int value = Integer.parseInt(clean);
|
||||||
|
return (int)Math.round((double)value * 1.52587890625);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
123
java-svc-jdk8/src/main/java/com/wordparse/core/ImageBO.java
Normal file
123
java-svc-jdk8/src/main/java/com/wordparse/core/ImageBO.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class ImageBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = 6640950035295880234L;
|
||||||
|
private InputStream is;
|
||||||
|
private Double width;
|
||||||
|
private Double height;
|
||||||
|
private Integer pictureType;
|
||||||
|
private String pictureUrl;
|
||||||
|
|
||||||
|
public InputStream getIs() {
|
||||||
|
return this.is;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getHeight() {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPictureType() {
|
||||||
|
return this.pictureType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPictureUrl() {
|
||||||
|
return this.pictureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setIs(InputStream is) {
|
||||||
|
this.is = is;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setWidth(Double width) {
|
||||||
|
this.width = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setHeight(Double height) {
|
||||||
|
this.height = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setPictureType(Integer pictureType) {
|
||||||
|
this.pictureType = pictureType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBO setPictureUrl(String pictureUrl) {
|
||||||
|
this.pictureUrl = pictureUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ImageBO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ImageBO other = (ImageBO)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InputStream this$is = this.getIs();
|
||||||
|
InputStream other$is = other.getIs();
|
||||||
|
if (this$is == null ? other$is != null : !this$is.equals(other$is)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$width = this.getWidth();
|
||||||
|
Double other$width = other.getWidth();
|
||||||
|
if (this$width == null ? other$width != null : !((Object)this$width).equals(other$width)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Double this$height = this.getHeight();
|
||||||
|
Double other$height = other.getHeight();
|
||||||
|
if (this$height == null ? other$height != null : !((Object)this$height).equals(other$height)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$pictureType = this.getPictureType();
|
||||||
|
Integer other$pictureType = other.getPictureType();
|
||||||
|
if (this$pictureType == null ? other$pictureType != null : !((Object)this$pictureType).equals(other$pictureType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$pictureUrl = this.getPictureUrl();
|
||||||
|
String other$pictureUrl = other.getPictureUrl();
|
||||||
|
return !(this$pictureUrl == null ? other$pictureUrl != null : !this$pictureUrl.equals(other$pictureUrl));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof ImageBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
InputStream $is = this.getIs();
|
||||||
|
result = result * 59 + ($is == null ? 43 : $is.hashCode());
|
||||||
|
Double $width = this.getWidth();
|
||||||
|
result = result * 59 + ($width == null ? 43 : ((Object)$width).hashCode());
|
||||||
|
Double $height = this.getHeight();
|
||||||
|
result = result * 59 + ($height == null ? 43 : ((Object)$height).hashCode());
|
||||||
|
Integer $pictureType = this.getPictureType();
|
||||||
|
result = result * 59 + ($pictureType == null ? 43 : ((Object)$pictureType).hashCode());
|
||||||
|
String $pictureUrl = this.getPictureUrl();
|
||||||
|
result = result * 59 + ($pictureUrl == null ? 43 : $pictureUrl.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ImageBO(is=" + this.getIs() + ", width=" + this.getWidth() + ", height=" + this.getHeight() + ", pictureType=" + this.getPictureType() + ", pictureUrl=" + this.getPictureUrl() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
109
java-svc-jdk8/src/main/java/com/wordparse/core/NumPr.java
Normal file
109
java-svc-jdk8/src/main/java/com/wordparse/core/NumPr.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class NumPr {
|
||||||
|
private BigInteger start;
|
||||||
|
private BigInteger current;
|
||||||
|
private String numFmt;
|
||||||
|
private String lvlText;
|
||||||
|
|
||||||
|
public NumPr() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NumPr(BigInteger start, BigInteger current, String numFmt, String lvlText) {
|
||||||
|
this.start = start;
|
||||||
|
this.current = current;
|
||||||
|
this.numFmt = numFmt;
|
||||||
|
this.lvlText = lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getStart() {
|
||||||
|
return this.start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigInteger getCurrent() {
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNumFmt() {
|
||||||
|
return this.numFmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLvlText() {
|
||||||
|
return this.lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStart(BigInteger start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrent(BigInteger current) {
|
||||||
|
this.current = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumFmt(String numFmt) {
|
||||||
|
this.numFmt = numFmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLvlText(String lvlText) {
|
||||||
|
this.lvlText = lvlText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof NumPr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
NumPr other = (NumPr)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BigInteger this$start = this.getStart();
|
||||||
|
BigInteger other$start = other.getStart();
|
||||||
|
if (this$start == null ? other$start != null : !((Object)this$start).equals(other$start)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
BigInteger this$current = this.getCurrent();
|
||||||
|
BigInteger other$current = other.getCurrent();
|
||||||
|
if (this$current == null ? other$current != null : !((Object)this$current).equals(other$current)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$numFmt = this.getNumFmt();
|
||||||
|
String other$numFmt = other.getNumFmt();
|
||||||
|
if (this$numFmt == null ? other$numFmt != null : !this$numFmt.equals(other$numFmt)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$lvlText = this.getLvlText();
|
||||||
|
String other$lvlText = other.getLvlText();
|
||||||
|
return !(this$lvlText == null ? other$lvlText != null : !this$lvlText.equals(other$lvlText));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof NumPr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
BigInteger $start = this.getStart();
|
||||||
|
result = result * 59 + ($start == null ? 43 : ((Object)$start).hashCode());
|
||||||
|
BigInteger $current = this.getCurrent();
|
||||||
|
result = result * 59 + ($current == null ? 43 : ((Object)$current).hashCode());
|
||||||
|
String $numFmt = this.getNumFmt();
|
||||||
|
result = result * 59 + ($numFmt == null ? 43 : $numFmt.hashCode());
|
||||||
|
String $lvlText = this.getLvlText();
|
||||||
|
result = result * 59 + ($lvlText == null ? 43 : $lvlText.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "NumPr(start=" + this.getStart() + ", current=" + this.getCurrent() + ", numFmt=" + this.getNumFmt() + ", lvlText=" + this.getLvlText() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
455
java-svc-jdk8/src/main/java/com/wordparse/core/ParseBO.java
Normal file
455
java-svc-jdk8/src/main/java/com/wordparse/core/ParseBO.java
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.fastjson.JSONObject
|
||||||
|
* org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.wordparse.core.ImageBO;
|
||||||
|
import com.wordparse.core.NumPr;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
|
||||||
|
|
||||||
|
public class ParseBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = -1386361606482288851L;
|
||||||
|
public static final String IMAGES = "images";
|
||||||
|
public static final String PARSE_ = "parse-";
|
||||||
|
public static final String OUTPUT_HTML = "output.html";
|
||||||
|
private static final String JBY_OPEN_OFFICE_XML_ = "JBY_OPEN_OFFICE_XML_";
|
||||||
|
private static final String JBY_IMAGE_DATA_ = "JBY_IMAGE_DATA_";
|
||||||
|
private static final String PLACEHOLDER_SUFFIX = "_END";
|
||||||
|
private static final String IMAGE_ = "image_";
|
||||||
|
private static final String JBY_EQ_LATEX_ = "JBY_EQ_LATEX_";
|
||||||
|
public static final String JBY_TEXTBOX = "JBY_TEXTBOX_JBY";
|
||||||
|
private static final String JBY_MATHTYPE_DATA_ = "JBY_MATHTYPE_DATA_";
|
||||||
|
private Path tempDir;
|
||||||
|
private String sourceFilePath;
|
||||||
|
private String sourceFileUrl;
|
||||||
|
private String sourceFileMd5;
|
||||||
|
private String sourceFileBakPath;
|
||||||
|
private String htmlPath;
|
||||||
|
private String imagesDir;
|
||||||
|
private String ossFolder;
|
||||||
|
private Transformer transformer;
|
||||||
|
private List<JSONObject> latexReplaceList;
|
||||||
|
private Map<String, String> imgMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> shapeImgMap;
|
||||||
|
private Map<String, String> openOfficeXmlMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> mathTypeMap = new HashMap<String, String>();
|
||||||
|
private Map<String, String> eqLatexMap = new HashMap<String, String>();
|
||||||
|
private StyleDefinitionsPart styleDefinitionsPart;
|
||||||
|
private Map<String, NumPr> numberingDefinitionsMap = new HashMap<String, NumPr>();
|
||||||
|
private Map<String, ImageBO> imageMap;
|
||||||
|
private Boolean newParseVersion;
|
||||||
|
private Integer count = 0;
|
||||||
|
private Long fileId;
|
||||||
|
|
||||||
|
public ParseBO() {
|
||||||
|
this.shapeImgMap = new HashMap<String, String>();
|
||||||
|
this.imageMap = new HashMap<String, ImageBO>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer incrCount() {
|
||||||
|
this.count = this.count + 1;
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOpenOfficeXmlPlaceholder() {
|
||||||
|
return JBY_OPEN_OFFICE_XML_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMathTypePlaceholder() {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMathTypePlaceholder(String ctOleObjectRid) {
|
||||||
|
return JBY_MATHTYPE_DATA_ + ctOleObjectRid + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImgName() {
|
||||||
|
return IMAGE_ + this.incrCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEqLatexPlaceholder() {
|
||||||
|
return JBY_EQ_LATEX_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getShapeImgPlaceholder() {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImagePlaceholder(String rid) {
|
||||||
|
return JBY_IMAGE_DATA_ + this.incrCount() + PLACEHOLDER_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getTempDir() {
|
||||||
|
return this.tempDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFilePath() {
|
||||||
|
return this.sourceFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileUrl() {
|
||||||
|
return this.sourceFileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileMd5() {
|
||||||
|
return this.sourceFileMd5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSourceFileBakPath() {
|
||||||
|
return this.sourceFileBakPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHtmlPath() {
|
||||||
|
return this.htmlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImagesDir() {
|
||||||
|
return this.imagesDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOssFolder() {
|
||||||
|
return this.ossFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformer getTransformer() {
|
||||||
|
return this.transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<JSONObject> getLatexReplaceList() {
|
||||||
|
return this.latexReplaceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getImgMap() {
|
||||||
|
return this.imgMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getShapeImgMap() {
|
||||||
|
return this.shapeImgMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getOpenOfficeXmlMap() {
|
||||||
|
return this.openOfficeXmlMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMathTypeMap() {
|
||||||
|
return this.mathTypeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getEqLatexMap() {
|
||||||
|
return this.eqLatexMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StyleDefinitionsPart getStyleDefinitionsPart() {
|
||||||
|
return this.styleDefinitionsPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, NumPr> getNumberingDefinitionsMap() {
|
||||||
|
return this.numberingDefinitionsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, ImageBO> getImageMap() {
|
||||||
|
return this.imageMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewParseVersion() {
|
||||||
|
return this.newParseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getCount() {
|
||||||
|
return this.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getFileId() {
|
||||||
|
return this.fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setTempDir(Path tempDir) {
|
||||||
|
this.tempDir = tempDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFilePath(String sourceFilePath) {
|
||||||
|
this.sourceFilePath = sourceFilePath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileUrl(String sourceFileUrl) {
|
||||||
|
this.sourceFileUrl = sourceFileUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileMd5(String sourceFileMd5) {
|
||||||
|
this.sourceFileMd5 = sourceFileMd5;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setSourceFileBakPath(String sourceFileBakPath) {
|
||||||
|
this.sourceFileBakPath = sourceFileBakPath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setHtmlPath(String htmlPath) {
|
||||||
|
this.htmlPath = htmlPath;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImagesDir(String imagesDir) {
|
||||||
|
this.imagesDir = imagesDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setOssFolder(String ossFolder) {
|
||||||
|
this.ossFolder = ossFolder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setTransformer(Transformer transformer) {
|
||||||
|
this.transformer = transformer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setLatexReplaceList(List<JSONObject> latexReplaceList) {
|
||||||
|
this.latexReplaceList = latexReplaceList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImgMap(Map<String, String> imgMap) {
|
||||||
|
this.imgMap = imgMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setShapeImgMap(Map<String, String> shapeImgMap) {
|
||||||
|
this.shapeImgMap = shapeImgMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setOpenOfficeXmlMap(Map<String, String> openOfficeXmlMap) {
|
||||||
|
this.openOfficeXmlMap = openOfficeXmlMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setMathTypeMap(Map<String, String> mathTypeMap) {
|
||||||
|
this.mathTypeMap = mathTypeMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setEqLatexMap(Map<String, String> eqLatexMap) {
|
||||||
|
this.eqLatexMap = eqLatexMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setStyleDefinitionsPart(StyleDefinitionsPart styleDefinitionsPart) {
|
||||||
|
this.styleDefinitionsPart = styleDefinitionsPart;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setNumberingDefinitionsMap(Map<String, NumPr> numberingDefinitionsMap) {
|
||||||
|
this.numberingDefinitionsMap = numberingDefinitionsMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setImageMap(Map<String, ImageBO> imageMap) {
|
||||||
|
this.imageMap = imageMap;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setNewParseVersion(Boolean newParseVersion) {
|
||||||
|
this.newParseVersion = newParseVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setCount(Integer count) {
|
||||||
|
this.count = count;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseBO setFileId(Long fileId) {
|
||||||
|
this.fileId = fileId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof ParseBO)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ParseBO other = (ParseBO)o;
|
||||||
|
if (!other.canEqual(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Path this$tempDir = this.getTempDir();
|
||||||
|
Path other$tempDir = other.getTempDir();
|
||||||
|
if (this$tempDir == null ? other$tempDir != null : !((Object)this$tempDir).equals(other$tempDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFilePath = this.getSourceFilePath();
|
||||||
|
String other$sourceFilePath = other.getSourceFilePath();
|
||||||
|
if (this$sourceFilePath == null ? other$sourceFilePath != null : !this$sourceFilePath.equals(other$sourceFilePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileUrl = this.getSourceFileUrl();
|
||||||
|
String other$sourceFileUrl = other.getSourceFileUrl();
|
||||||
|
if (this$sourceFileUrl == null ? other$sourceFileUrl != null : !this$sourceFileUrl.equals(other$sourceFileUrl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileMd5 = this.getSourceFileMd5();
|
||||||
|
String other$sourceFileMd5 = other.getSourceFileMd5();
|
||||||
|
if (this$sourceFileMd5 == null ? other$sourceFileMd5 != null : !this$sourceFileMd5.equals(other$sourceFileMd5)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$sourceFileBakPath = this.getSourceFileBakPath();
|
||||||
|
String other$sourceFileBakPath = other.getSourceFileBakPath();
|
||||||
|
if (this$sourceFileBakPath == null ? other$sourceFileBakPath != null : !this$sourceFileBakPath.equals(other$sourceFileBakPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$htmlPath = this.getHtmlPath();
|
||||||
|
String other$htmlPath = other.getHtmlPath();
|
||||||
|
if (this$htmlPath == null ? other$htmlPath != null : !this$htmlPath.equals(other$htmlPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$imagesDir = this.getImagesDir();
|
||||||
|
String other$imagesDir = other.getImagesDir();
|
||||||
|
if (this$imagesDir == null ? other$imagesDir != null : !this$imagesDir.equals(other$imagesDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String this$ossFolder = this.getOssFolder();
|
||||||
|
String other$ossFolder = other.getOssFolder();
|
||||||
|
if (this$ossFolder == null ? other$ossFolder != null : !this$ossFolder.equals(other$ossFolder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Transformer this$transformer = this.getTransformer();
|
||||||
|
Transformer other$transformer = other.getTransformer();
|
||||||
|
if (this$transformer == null ? other$transformer != null : !this$transformer.equals(other$transformer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<JSONObject> this$latexReplaceList = this.getLatexReplaceList();
|
||||||
|
List<JSONObject> other$latexReplaceList = other.getLatexReplaceList();
|
||||||
|
if (this$latexReplaceList == null ? other$latexReplaceList != null : !((Object)this$latexReplaceList).equals(other$latexReplaceList)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$imgMap = this.getImgMap();
|
||||||
|
Map<String, String> other$imgMap = other.getImgMap();
|
||||||
|
if (this$imgMap == null ? other$imgMap != null : !((Object)this$imgMap).equals(other$imgMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$shapeImgMap = this.getShapeImgMap();
|
||||||
|
Map<String, String> other$shapeImgMap = other.getShapeImgMap();
|
||||||
|
if (this$shapeImgMap == null ? other$shapeImgMap != null : !((Object)this$shapeImgMap).equals(other$shapeImgMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$openOfficeXmlMap = this.getOpenOfficeXmlMap();
|
||||||
|
Map<String, String> other$openOfficeXmlMap = other.getOpenOfficeXmlMap();
|
||||||
|
if (this$openOfficeXmlMap == null ? other$openOfficeXmlMap != null : !((Object)this$openOfficeXmlMap).equals(other$openOfficeXmlMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$mathTypeMap = this.getMathTypeMap();
|
||||||
|
Map<String, String> other$mathTypeMap = other.getMathTypeMap();
|
||||||
|
if (this$mathTypeMap == null ? other$mathTypeMap != null : !((Object)this$mathTypeMap).equals(other$mathTypeMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, String> this$eqLatexMap = this.getEqLatexMap();
|
||||||
|
Map<String, String> other$eqLatexMap = other.getEqLatexMap();
|
||||||
|
if (this$eqLatexMap == null ? other$eqLatexMap != null : !((Object)this$eqLatexMap).equals(other$eqLatexMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StyleDefinitionsPart this$styleDefinitionsPart = this.getStyleDefinitionsPart();
|
||||||
|
StyleDefinitionsPart other$styleDefinitionsPart = other.getStyleDefinitionsPart();
|
||||||
|
if (this$styleDefinitionsPart == null ? other$styleDefinitionsPart != null : !this$styleDefinitionsPart.equals(other$styleDefinitionsPart)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, NumPr> this$numberingDefinitionsMap = this.getNumberingDefinitionsMap();
|
||||||
|
Map<String, NumPr> other$numberingDefinitionsMap = other.getNumberingDefinitionsMap();
|
||||||
|
if (this$numberingDefinitionsMap == null ? other$numberingDefinitionsMap != null : !((Object)this$numberingDefinitionsMap).equals(other$numberingDefinitionsMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Map<String, ImageBO> this$imageMap = this.getImageMap();
|
||||||
|
Map<String, ImageBO> other$imageMap = other.getImageMap();
|
||||||
|
if (this$imageMap == null ? other$imageMap != null : !((Object)this$imageMap).equals(other$imageMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Boolean this$newParseVersion = this.getNewParseVersion();
|
||||||
|
Boolean other$newParseVersion = other.getNewParseVersion();
|
||||||
|
if (this$newParseVersion == null ? other$newParseVersion != null : !((Object)this$newParseVersion).equals(other$newParseVersion)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Integer this$count = this.getCount();
|
||||||
|
Integer other$count = other.getCount();
|
||||||
|
if (this$count == null ? other$count != null : !((Object)this$count).equals(other$count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Long this$fileId = this.getFileId();
|
||||||
|
Long other$fileId = other.getFileId();
|
||||||
|
return !(this$fileId == null ? other$fileId != null : !((Object)this$fileId).equals(other$fileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean canEqual(Object other) {
|
||||||
|
return other instanceof ParseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
int PRIME = 59;
|
||||||
|
int result = 1;
|
||||||
|
Path $tempDir = this.getTempDir();
|
||||||
|
result = result * 59 + ($tempDir == null ? 43 : ((Object)$tempDir).hashCode());
|
||||||
|
String $sourceFilePath = this.getSourceFilePath();
|
||||||
|
result = result * 59 + ($sourceFilePath == null ? 43 : $sourceFilePath.hashCode());
|
||||||
|
String $sourceFileUrl = this.getSourceFileUrl();
|
||||||
|
result = result * 59 + ($sourceFileUrl == null ? 43 : $sourceFileUrl.hashCode());
|
||||||
|
String $sourceFileMd5 = this.getSourceFileMd5();
|
||||||
|
result = result * 59 + ($sourceFileMd5 == null ? 43 : $sourceFileMd5.hashCode());
|
||||||
|
String $sourceFileBakPath = this.getSourceFileBakPath();
|
||||||
|
result = result * 59 + ($sourceFileBakPath == null ? 43 : $sourceFileBakPath.hashCode());
|
||||||
|
String $htmlPath = this.getHtmlPath();
|
||||||
|
result = result * 59 + ($htmlPath == null ? 43 : $htmlPath.hashCode());
|
||||||
|
String $imagesDir = this.getImagesDir();
|
||||||
|
result = result * 59 + ($imagesDir == null ? 43 : $imagesDir.hashCode());
|
||||||
|
String $ossFolder = this.getOssFolder();
|
||||||
|
result = result * 59 + ($ossFolder == null ? 43 : $ossFolder.hashCode());
|
||||||
|
Transformer $transformer = this.getTransformer();
|
||||||
|
result = result * 59 + ($transformer == null ? 43 : $transformer.hashCode());
|
||||||
|
List<JSONObject> $latexReplaceList = this.getLatexReplaceList();
|
||||||
|
result = result * 59 + ($latexReplaceList == null ? 43 : ((Object)$latexReplaceList).hashCode());
|
||||||
|
Map<String, String> $imgMap = this.getImgMap();
|
||||||
|
result = result * 59 + ($imgMap == null ? 43 : ((Object)$imgMap).hashCode());
|
||||||
|
Map<String, String> $shapeImgMap = this.getShapeImgMap();
|
||||||
|
result = result * 59 + ($shapeImgMap == null ? 43 : ((Object)$shapeImgMap).hashCode());
|
||||||
|
Map<String, String> $openOfficeXmlMap = this.getOpenOfficeXmlMap();
|
||||||
|
result = result * 59 + ($openOfficeXmlMap == null ? 43 : ((Object)$openOfficeXmlMap).hashCode());
|
||||||
|
Map<String, String> $mathTypeMap = this.getMathTypeMap();
|
||||||
|
result = result * 59 + ($mathTypeMap == null ? 43 : ((Object)$mathTypeMap).hashCode());
|
||||||
|
Map<String, String> $eqLatexMap = this.getEqLatexMap();
|
||||||
|
result = result * 59 + ($eqLatexMap == null ? 43 : ((Object)$eqLatexMap).hashCode());
|
||||||
|
StyleDefinitionsPart $styleDefinitionsPart = this.getStyleDefinitionsPart();
|
||||||
|
result = result * 59 + ($styleDefinitionsPart == null ? 43 : $styleDefinitionsPart.hashCode());
|
||||||
|
Map<String, NumPr> $numberingDefinitionsMap = this.getNumberingDefinitionsMap();
|
||||||
|
result = result * 59 + ($numberingDefinitionsMap == null ? 43 : ((Object)$numberingDefinitionsMap).hashCode());
|
||||||
|
Map<String, ImageBO> $imageMap = this.getImageMap();
|
||||||
|
result = result * 59 + ($imageMap == null ? 43 : ((Object)$imageMap).hashCode());
|
||||||
|
Boolean $newParseVersion = this.getNewParseVersion();
|
||||||
|
result = result * 59 + ($newParseVersion == null ? 43 : ((Object)$newParseVersion).hashCode());
|
||||||
|
Integer $count = this.getCount();
|
||||||
|
result = result * 59 + ($count == null ? 43 : ((Object)$count).hashCode());
|
||||||
|
Long $fileId = this.getFileId();
|
||||||
|
result = result * 59 + ($fileId == null ? 43 : ((Object)$fileId).hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ParseBO(tempDir=" + this.getTempDir() + ", sourceFilePath=" + this.getSourceFilePath() + ", sourceFileUrl=" + this.getSourceFileUrl() + ", sourceFileMd5=" + this.getSourceFileMd5() + ", sourceFileBakPath=" + this.getSourceFileBakPath() + ", htmlPath=" + this.getHtmlPath() + ", imagesDir=" + this.getImagesDir() + ", ossFolder=" + this.getOssFolder() + ", transformer=" + this.getTransformer() + ", latexReplaceList=" + this.getLatexReplaceList() + ", imgMap=" + this.getImgMap() + ", shapeImgMap=" + this.getShapeImgMap() + ", openOfficeXmlMap=" + this.getOpenOfficeXmlMap() + ", mathTypeMap=" + this.getMathTypeMap() + ", eqLatexMap=" + this.getEqLatexMap() + ", styleDefinitionsPart=" + this.getStyleDefinitionsPart() + ", numberingDefinitionsMap=" + this.getNumberingDefinitionsMap() + ", imageMap=" + this.getImageMap() + ", newParseVersion=" + this.getNewParseVersion() + ", count=" + this.getCount() + ", fileId=" + this.getFileId() + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
140
java-svc-jdk8/src/main/java/com/wordparse/core/TagEnum.java
Normal file
140
java-svc-jdk8/src/main/java/com/wordparse/core/TagEnum.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.wordparse.core.DistinctUtils
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.jsoup.nodes.Element
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.DistinctUtils;
|
||||||
|
import com.wordparse.core.TagStyleBO;
|
||||||
|
import com.wordparse.core.AttrEnum;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
public enum TagEnum {
|
||||||
|
H1("h1", true, null),
|
||||||
|
H2("h2", true, null),
|
||||||
|
H3("h3", true, null),
|
||||||
|
H4("h4", true, null),
|
||||||
|
P("p", false, null),
|
||||||
|
DIV("div", false, null),
|
||||||
|
FONT("font", false, Stream.of(AttrEnum.FACE, AttrEnum.SIZE, AttrEnum.COLOR).collect(Collectors.toList())),
|
||||||
|
STRIKE("strike", true, null),
|
||||||
|
BR("br", false, null),
|
||||||
|
PBR("pbr", false, null),
|
||||||
|
HR("hr", false, null),
|
||||||
|
U("u", true, null),
|
||||||
|
I("i", true, null),
|
||||||
|
SUB("sub", true, null),
|
||||||
|
SUP("sup", true, null),
|
||||||
|
WAVE("wave", true, null),
|
||||||
|
DOT("dot", true, null),
|
||||||
|
B("b", true, null),
|
||||||
|
A("a", false, Stream.of(AttrEnum.HREF).collect(Collectors.toList())),
|
||||||
|
TABLE("table", false, null),
|
||||||
|
UL("ul", false, null),
|
||||||
|
OL("ol", false, null),
|
||||||
|
LI("li", false, null),
|
||||||
|
IMG("img", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
IMQ("imq", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
VIDEO("video", false, Stream.of(AttrEnum.WIDTH, AttrEnum.HEIGHT).collect(Collectors.toList())),
|
||||||
|
SOURCE("source", false, null),
|
||||||
|
MATH("math", false, null),
|
||||||
|
TAB("tab", false, null),
|
||||||
|
TEXT("#text", false, null),
|
||||||
|
SPAN("span", false, Stream.of(AttrEnum.CLASS).collect(Collectors.toList())),
|
||||||
|
EM("em", true, null),
|
||||||
|
STRONG("strong", true, null);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Boolean dealTagStyle;
|
||||||
|
private final List<AttrEnum> attrList;
|
||||||
|
public static final Set<String> H_P_SET;
|
||||||
|
public static final Set<String> H_SET;
|
||||||
|
public static final Set<String> UL_OL_SET;
|
||||||
|
private static final Map<String, TagEnum> MAP;
|
||||||
|
|
||||||
|
public static TagEnum getTagEnum(String tagName) {
|
||||||
|
return MAP.get(tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<TagStyleBO> getStyleList(Element element) {
|
||||||
|
ArrayList<TagStyleBO> styleList = new ArrayList<TagStyleBO>();
|
||||||
|
TagEnum tagEnum = TagEnum.getTagEnum(element.tagName());
|
||||||
|
if (Objects.nonNull((Object)tagEnum) && Boolean.TRUE.equals(tagEnum.dealTagStyle)) {
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(tagEnum.getName()).setValue(tagEnum.getName()));
|
||||||
|
}
|
||||||
|
if (Objects.nonNull((Object)tagEnum) && CollectionUtils.isNotEmpty(tagEnum.getAttrList())) {
|
||||||
|
for (AttrEnum attrEnum : tagEnum.getAttrList()) {
|
||||||
|
if (!StringUtils.isNotBlank((CharSequence)element.attr(attrEnum.getAttr())) || "null".equals(element.attr(attrEnum.getAttr()))) continue;
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(attrEnum.getAttr()).setValue(element.attr(attrEnum.getAttr())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> styles = StringUtils.isBlank((CharSequence)element.attr(AttrEnum.STYLE.getAttr())) ? Collections.emptyList() : Arrays.asList(element.attr(AttrEnum.STYLE.getAttr()).split(";"));
|
||||||
|
styles.forEach(style -> {
|
||||||
|
if (StringUtils.isBlank((CharSequence)style)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String[] split = style.split(":");
|
||||||
|
if (split.length < 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String attr = split[0].trim();
|
||||||
|
String value = split[1].trim();
|
||||||
|
if (StringUtils.isBlank((CharSequence)value) || "null".equals(value) || StringUtils.isBlank((CharSequence)attr) || "null".equals(attr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
styleList.add(new TagStyleBO().setTagName(element.tagName()).setAttr(attr).setValue(value));
|
||||||
|
});
|
||||||
|
return styleList.stream().filter(DistinctUtils.distinctByKey(TagStyleBO::getAttr)).filter(e -> !e.getValue().contains("auto")).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, String> getStyleMap(Element element) {
|
||||||
|
List<TagStyleBO> styleList = TagEnum.getStyleList(element);
|
||||||
|
return styleList.stream().collect(Collectors.toMap(TagStyleBO::getAttr, TagStyleBO::getValue, (l, r) -> l));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getDealTagStyle() {
|
||||||
|
return this.dealTagStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AttrEnum> getAttrList() {
|
||||||
|
return this.attrList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TagEnum(String name, Boolean dealTagStyle, List<AttrEnum> attrList) {
|
||||||
|
this.name = name;
|
||||||
|
this.dealTagStyle = dealTagStyle;
|
||||||
|
this.attrList = attrList;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
H_P_SET = Stream.of(H1.getName(), H2.getName(), H3.getName(), H4.getName(), P.getName()).collect(Collectors.toSet());
|
||||||
|
H_SET = Stream.of(H1.getName(), H2.getName(), H3.getName(), H4.getName()).collect(Collectors.toSet());
|
||||||
|
UL_OL_SET = Stream.of(UL.getName(), OL.getName()).collect(Collectors.toSet());
|
||||||
|
MAP = new HashMap<String, TagEnum>();
|
||||||
|
for (TagEnum tagEnum : TagEnum.values()) {
|
||||||
|
MAP.put(tagEnum.getName(), tagEnum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class TagStyleBO
|
||||||
|
implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5541312317983576319L;
|
||||||
|
private String tagName;
|
||||||
|
private String attr;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public String getTagName() {
|
||||||
|
return this.tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttr() {
|
||||||
|
return this.attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setTagName(String tagName) {
|
||||||
|
this.tagName = tagName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setAttr(String attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1791
java-svc-jdk8/src/main/java/com/wordparse/core/WordParseUtil.java
Normal file
1791
java-svc-jdk8/src/main/java/com/wordparse/core/WordParseUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
36
java-svc/Dockerfile
Normal file
36
java-svc/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# WordFileParse Java Service — Docker build with pre-built JAR
|
||||||
|
# First build JAR locally: mvn package -DskipTests
|
||||||
|
# Then: docker build -t wordparse-java -f java-svc/Dockerfile .
|
||||||
|
FROM eclipse-temurin:17-jdk
|
||||||
|
|
||||||
|
# Install Ruby + Python + build deps
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
python3 python3-pip python3-venv \
|
||||||
|
ruby ruby-dev build-essential libxml2-dev libxslt-dev pkg-config \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install pre-built Ruby gems (except nokogiri — compiled for current Ruby)
|
||||||
|
COPY scripts/mathtype2mathml/install/gems/one/*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/two/bindata-*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/two/concurrent-ruby-*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/two/mini_portile2-*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/two/pry-*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/two/ruby-ole-*.gem \
|
||||||
|
scripts/mathtype2mathml/install/gems/third/parallel-*.gem \
|
||||||
|
/tmp/gems/
|
||||||
|
RUN gem install /tmp/gems/*.gem --no-doc && rm -rf /tmp/gems
|
||||||
|
RUN gem install nokogiri --no-doc
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy transform scripts
|
||||||
|
COPY scripts/ /opt/transform/
|
||||||
|
|
||||||
|
# Python deps for omml_mathml2latex
|
||||||
|
RUN pip3 install --break-system-packages -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple lxml
|
||||||
|
|
||||||
|
# Copy pre-built JAR
|
||||||
|
COPY java-svc/target/word-file-parse-1.0.0.jar /app/app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["java", "-jar", "/app/app.jar"]
|
||||||
136
java-svc/pom.xml
Normal file
136
java-svc/pom.xml
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.wordparse</groupId>
|
||||||
|
<artifactId>word-file-parse</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>WordFileParse</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<docx4j.version>8.3.9</docx4j.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Docx4J 8.x (Word → HTML) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j-core</artifactId>
|
||||||
|
<version>${docx4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
|
||||||
|
<version>${docx4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jsoup (HTML post-processing) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.17.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Apache Commons -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-collections4</artifactId>
|
||||||
|
<version>4.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.16.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- FastJSON -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>2.0.51</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- AWS S3 SDK (RustFS) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3</artifactId>
|
||||||
|
<version>2.25.27</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>2.3.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.saxon</groupId>
|
||||||
|
<artifactId>Saxon-HE</artifactId>
|
||||||
|
<version>12.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- ImageIO for TIFF handling -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-tiff</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.32</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.wordparse;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableConfigurationProperties
|
||||||
|
public class WordParseApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(WordParseApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.wordparse.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "wordparse")
|
||||||
|
public class WordParseConfig {
|
||||||
|
private String tmpPath = System.getProperty("java.io.tmpdir");
|
||||||
|
private Double imageScalingFactor = 2.0;
|
||||||
|
private Map<String, String[]> cleanStyleMap = Map.of();
|
||||||
|
private String transformScriptsPath = "/opt/transform";
|
||||||
|
private String pythonCommand = "python3";
|
||||||
|
|
||||||
|
public String getTmpPath() { return tmpPath; }
|
||||||
|
public Double getImageScalingFactor() { return imageScalingFactor; }
|
||||||
|
public Map<String, String[]> getCleanStyleMap() { return cleanStyleMap; }
|
||||||
|
public String getTransformScriptsPath() { return transformScriptsPath; }
|
||||||
|
public String getPythonCommand() { return pythonCommand; }
|
||||||
|
|
||||||
|
public void setTmpPath(String v) { this.tmpPath = v; }
|
||||||
|
public void setImageScalingFactor(Double v) { this.imageScalingFactor = v; }
|
||||||
|
public void setCleanStyleMap(Map<String, String[]> v) { this.cleanStyleMap = v; }
|
||||||
|
public void setTransformScriptsPath(String v) { this.transformScriptsPath = v; }
|
||||||
|
public void setPythonCommand(String v) { this.pythonCommand = v; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.wordparse.controller;
|
||||||
|
|
||||||
|
import com.wordparse.core.WordParseUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v3/server_office_file/file")
|
||||||
|
public class WordParseController {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(WordParseController.class);
|
||||||
|
|
||||||
|
private final WordParseUtil wordParseUtil;
|
||||||
|
|
||||||
|
public WordParseController(WordParseUtil wordParseUtil) {
|
||||||
|
this.wordParseUtil = wordParseUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/parse", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public Map<String, Object> parseFile(
|
||||||
|
@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value = "fileId", required = false) String fileId) {
|
||||||
|
|
||||||
|
if (file.isEmpty()) throw new IllegalArgumentException("File is empty");
|
||||||
|
|
||||||
|
String name = file.getOriginalFilename();
|
||||||
|
if (name == null || !name.toLowerCase().endsWith(".docx")) {
|
||||||
|
throw new IllegalArgumentException("Only .docx files supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String html = wordParseUtil.parseBytes(file.getBytes());
|
||||||
|
String fid = fileId != null ? fileId : UUID.randomUUID().toString();
|
||||||
|
log.info("Parse success: file={}, fileId={}, htmlLen={}", name, fid, html.length());
|
||||||
|
return Map.of("fileId", fid, "content", html);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Parse failed: {}", name, e);
|
||||||
|
throw new RuntimeException("Parse failed: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/health")
|
||||||
|
public Map<String, String> health() {
|
||||||
|
return Map.of("status", "ok");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
|
||||||
|
public class CtObjectFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtObjectFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof CTOLEObject) {
|
||||||
|
CTOLEObject ctoleObject = (CTOLEObject)o;
|
||||||
|
RelationshipsPart rsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
if (Objects.nonNull(ctoleObject.getId())) {
|
||||||
|
Part part = rsPart.getPart(ctoleObject.getId());
|
||||||
|
String binFilePath = part.getPartName().getURI().getPath();
|
||||||
|
String placeholder = this.parseBO.getMathTypePlaceholder(ctoleObject.getId());
|
||||||
|
this.parseBO.getMathTypeMap().put(placeholder, this.parseBO.getTempDir() + "/temp" + binFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
123
java-svc/src/main/java/com/wordparse/core/CtTextBoxFinder.java
Normal file
123
java-svc/src/main/java/com/wordparse/core/CtTextBoxFinder.java
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TextUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.mce.AlternateContent
|
||||||
|
* org.docx4j.mce.AlternateContent$Fallback
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.vml.CTGroup
|
||||||
|
* org.docx4j.vml.CTRoundRect
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.CTTextbox
|
||||||
|
* org.docx4j.vml.VmlShapeElements
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TextUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.mce.AlternateContent;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.vml.CTGroup;
|
||||||
|
import org.docx4j.vml.CTRoundRect;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.CTTextbox;
|
||||||
|
import org.docx4j.vml.VmlShapeElements;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
|
public class CtTextBoxFinder extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtTextBoxFinder.class);
|
||||||
|
private WordprocessingMLPackage wordMlPackage;
|
||||||
|
private ParseBO parseBO;
|
||||||
|
|
||||||
|
public CtTextBoxFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof Pict) {
|
||||||
|
boolean isFirstRun = true;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
R sourceR = factory.createR();
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
List anyAndAny = pict.getAnyAndAny();
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
if (!(object instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement jbe = (JAXBElement)object;
|
||||||
|
this.findTextBox(jbe.getValue(), stringBuilder, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)stringBuilder.toString())) {
|
||||||
|
AlternateContent.Fallback fallback = (AlternateContent.Fallback)pict.getParent();
|
||||||
|
AlternateContent alternateContent = (AlternateContent)fallback.getParent();
|
||||||
|
R r = (R)alternateContent.getParent();
|
||||||
|
Text text = factory.createText();
|
||||||
|
text.setValue(stringBuilder + "WP_TEXTBOX_WP");
|
||||||
|
r.getContent().set(0, text);
|
||||||
|
r.setRPr(sourceR.getRPr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findTextBox(Object object, StringBuilder sb, boolean isFirstRun, R sourceR) {
|
||||||
|
block8: {
|
||||||
|
List egShapeElements;
|
||||||
|
if (object instanceof CTShape || object instanceof CTRoundRect) {
|
||||||
|
VmlShapeElements vmlShapeElements = (VmlShapeElements)object;
|
||||||
|
egShapeElements = vmlShapeElements.getEGShapeElements();
|
||||||
|
for (Object o : egShapeElements) {
|
||||||
|
JAXBElement egShapeElement = (JAXBElement) o;
|
||||||
|
if (!(egShapeElement.getValue() instanceof CTTextbox)) continue;
|
||||||
|
CTTextbox ctTextbox = (CTTextbox)egShapeElement.getValue();
|
||||||
|
CTTxbxContent txbxContent = ctTextbox.getTxbxContent();
|
||||||
|
for (int i = 0; i < txbxContent.getContent().size(); ++i) {
|
||||||
|
Object obj = txbxContent.getContent().get(i);
|
||||||
|
String text = TextUtils.getText(obj);
|
||||||
|
if (txbxContent.getContent().size() > 1 && i != txbxContent.getContent().size() - 1) {
|
||||||
|
text = text + "WP_TEXTBOX_BR";
|
||||||
|
}
|
||||||
|
sb.append(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(object instanceof CTGroup)) break block8;
|
||||||
|
CTGroup ctGroup = (CTGroup)object;
|
||||||
|
egShapeElements = ctGroup.getEGShapeElements();
|
||||||
|
boolean isCtShape = egShapeElements.stream().anyMatch(f -> ((JAXBElement)f).getValue() instanceof CTShape);
|
||||||
|
if (isCtShape) {
|
||||||
|
for (int i = egShapeElements.size() - 1; i >= 0; --i) {
|
||||||
|
this.findTextBox(((JAXBElement)egShapeElements.get(i)).getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Object o : egShapeElements) {
|
||||||
|
JAXBElement egShapeElement = (JAXBElement) o;
|
||||||
|
this.findTextBox(egShapeElement.getValue(), sb, isFirstRun, sourceR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
421
java-svc/src/main/java/com/wordparse/core/CtoMathFinder.java
Normal file
421
java-svc/src/main/java/com/wordparse/core/CtoMathFinder.java
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.apache.commons.lang3.StringUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.XmlUtils
|
||||||
|
* org.docx4j.math.CTOMath
|
||||||
|
* org.docx4j.math.CTOMathPara
|
||||||
|
* org.docx4j.wml.CTTxbxContent
|
||||||
|
* org.docx4j.wml.CTVerticalAlignRun
|
||||||
|
* org.docx4j.wml.FldChar
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.P
|
||||||
|
* org.docx4j.wml.PPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr
|
||||||
|
* org.docx4j.wml.PPrBase$NumPr$Ilvl
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.RPrAbstract
|
||||||
|
* org.docx4j.wml.STFldCharType
|
||||||
|
* org.docx4j.wml.Style
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.docx4j.wml.U
|
||||||
|
* org.docx4j.wml.UnderlineEnumeration
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import com.wordparse.core.NumPr;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.XmlUtils;
|
||||||
|
import org.docx4j.math.CTOMath;
|
||||||
|
import org.docx4j.math.CTOMathPara;
|
||||||
|
import org.docx4j.wml.CTTxbxContent;
|
||||||
|
import org.docx4j.wml.CTVerticalAlignRun;
|
||||||
|
import org.docx4j.wml.FldChar;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.P;
|
||||||
|
import org.docx4j.wml.PPr;
|
||||||
|
import org.docx4j.wml.PPrBase;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.RPrAbstract;
|
||||||
|
import org.docx4j.wml.STFldCharType;
|
||||||
|
import org.docx4j.wml.Style;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.docx4j.wml.U;
|
||||||
|
import org.docx4j.wml.UnderlineEnumeration;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CtoMathFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CtoMathFinder.class);
|
||||||
|
private ParseBO parseBO;
|
||||||
|
private static final Pattern CIRCLED_NUMBER_PATTERN = Pattern.compile("^\\s*eq\\s*\\\\o\\((?:\u25cb,\\\\s\\\\up\\d+\\((\\d+)\\)|\\\\s\\\\up\\d+\\((\\d+)\\),\u25cb)\\)");
|
||||||
|
|
||||||
|
public CtoMathFinder(ParseBO parseBO) {
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
if (o instanceof P) {
|
||||||
|
Text text;
|
||||||
|
Style style;
|
||||||
|
CTTxbxContent ctTxbxContent;
|
||||||
|
StringBuilder eqCode = new StringBuilder();
|
||||||
|
StringBuilder originEqCode = new StringBuilder();
|
||||||
|
boolean inFiled = false;
|
||||||
|
boolean isEq = false;
|
||||||
|
boolean superScriptFlag = false;
|
||||||
|
boolean subScriptFlag = false;
|
||||||
|
ArrayList<Integer> fieldRunIndexs = new ArrayList<Integer>();
|
||||||
|
P paragraph = (P)o;
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
boolean isNumPr = true;
|
||||||
|
if (paragraph.getParent() instanceof CTTxbxContent) {
|
||||||
|
isNumPr = false;
|
||||||
|
}
|
||||||
|
PPr pPr = paragraph.getPPr();
|
||||||
|
PPrBase.NumPr numPr = null;
|
||||||
|
if (Objects.nonNull(pPr)) {
|
||||||
|
numPr = pPr.getNumPr();
|
||||||
|
}
|
||||||
|
if (Objects.isNull(numPr) && Objects.nonNull(pPr) && Objects.nonNull(pPr.getPStyle()) && this.parseBO.getStyleDefinitionsPart() != null) {
|
||||||
|
// Style-based numPr lookup — skipped (StyleDefinitionsPart not ported)
|
||||||
|
}
|
||||||
|
if (isNumPr && Objects.nonNull(numPr) && Objects.nonNull(numPr.getNumId()) && Objects.nonNull(numPr.getIlvl())) {
|
||||||
|
String numPrKey = numPr.getNumId().getVal() + "_" + numPr.getIlvl().getVal();
|
||||||
|
Map<String, NumPr> numberingDefinitionsMap = this.parseBO.getNumberingDefinitionsMap();
|
||||||
|
if (numberingDefinitionsMap.containsKey(numPrKey)) {
|
||||||
|
NumPr currentNumPr = numberingDefinitionsMap.get(numPrKey);
|
||||||
|
String lvlText = currentNumPr.getLvlText();
|
||||||
|
for (int i = 0; i <= numPr.getIlvl().getVal().intValue(); ++i) {
|
||||||
|
NumPr numPrDefine;
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
currentNumPr.setCurrent(currentNumPr.getCurrent().add(BigInteger.ONE));
|
||||||
|
}
|
||||||
|
String rep = String.valueOf((numPrDefine = numberingDefinitionsMap.get(numPr.getNumId().getVal() + "_" + i)).getCurrent().compareTo(BigInteger.ZERO) == 0 ? numPrDefine.getStart() : numPrDefine.getCurrent());
|
||||||
|
if (i == numPr.getIlvl().getVal().intValue()) {
|
||||||
|
switch (currentNumPr.getNumFmt()) {
|
||||||
|
case "UPPER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "DECIMAL_ZERO": {
|
||||||
|
rep = CtoMathFinder.strToDecimalZero(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "UPPER_LETTER": {
|
||||||
|
rep = CtoMathFinder.strToLowerLetter(rep).toUpperCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "LOWER_ROMAN": {
|
||||||
|
rep = this.strToUpperRoman(rep).toLowerCase();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CHINESE_COUNTING":
|
||||||
|
case "CHINESE_COUNTING_THOUSAND": {
|
||||||
|
rep = this.strToChineseCounting(rep);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lvlText = lvlText.replace("%" + (i + 1), rep);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)lvlText)) {
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(lvlText + " ");
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().add(0, run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < paragraph.getContent().size(); ++i) {
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
Object content = paragraph.getContent().get(i);
|
||||||
|
if (content instanceof JAXBElement && ((jaxbElement = (JAXBElement)content).getValue() instanceof CTOMath || jaxbElement.getValue() instanceof CTOMathPara)) {
|
||||||
|
String placeholder = "";
|
||||||
|
if (Boolean.TRUE.equals(this.parseBO.getNewParseVersion())) {
|
||||||
|
String omml = XmlUtils.marshaltoString((Object)jaxbElement);
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, omml);
|
||||||
|
} else {
|
||||||
|
String mathMl = WordParseUtil.getMathMl(jaxbElement, this.parseBO.getTransformer());
|
||||||
|
placeholder = this.parseBO.getOpenOfficeXmlPlaceholder();
|
||||||
|
this.parseBO.getOpenOfficeXmlMap().put(placeholder, mathMl);
|
||||||
|
}
|
||||||
|
R run = factory.createR();
|
||||||
|
text = factory.createText();
|
||||||
|
text.setValue(placeholder);
|
||||||
|
run.getContent().add(text);
|
||||||
|
paragraph.getContent().set(i, run);
|
||||||
|
}
|
||||||
|
if (!(content instanceof R)) continue;
|
||||||
|
R run = (R)content;
|
||||||
|
for (int j = 0; j < run.getContent().size(); ++j) {
|
||||||
|
Object runC = run.getContent().get(j);
|
||||||
|
if (!(runC instanceof JAXBElement)) continue;
|
||||||
|
JAXBElement runCj = (JAXBElement)runC;
|
||||||
|
if (runCj.getValue() instanceof FldChar) {
|
||||||
|
FldChar fldChar = (FldChar)runCj.getValue();
|
||||||
|
if (fldChar.getFldCharType().equals((Object)STFldCharType.BEGIN)) {
|
||||||
|
eqCode = new StringBuilder();
|
||||||
|
originEqCode = new StringBuilder();
|
||||||
|
inFiled = true;
|
||||||
|
isEq = false;
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
fieldRunIndexs = new ArrayList();
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && fldChar.getFldCharType().equals((Object)STFldCharType.END)) {
|
||||||
|
if (isEq) {
|
||||||
|
String placeholder;
|
||||||
|
Matcher matcher = CIRCLED_NUMBER_PATTERN.matcher(originEqCode);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
String numStr = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder() + "WPEQNUM";
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, CtoMathFinder.getCircledNumber(numStr));
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text2 = factory.createText();
|
||||||
|
text2.setValue(placeholder);
|
||||||
|
runN.getContent().add(text2);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)eqCode.toString())) {
|
||||||
|
String latex = EqToLatexUtil.convert(eqCode.toString());
|
||||||
|
latex = latex.replaceAll("<", "<").replaceAll(">", ">");
|
||||||
|
placeholder = this.parseBO.getEqLatexPlaceholder() + "WPEQNUM";
|
||||||
|
this.parseBO.getEqLatexMap().put(placeholder, latex);
|
||||||
|
R runN = factory.createR();
|
||||||
|
Text text3 = factory.createText();
|
||||||
|
text3.setValue(placeholder);
|
||||||
|
runN.getContent().add(text3);
|
||||||
|
paragraph.getContent().set(i, runN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
isEq = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inFiled && isEq && !fldChar.getFldCharType().value().equals("separate")) continue;
|
||||||
|
}
|
||||||
|
if (!inFiled || !(runCj.getValue() instanceof Text)) continue;
|
||||||
|
CTVerticalAlignRun vertAlign = Optional.ofNullable(run.getRPr()).map(RPrAbstract::getVertAlign).orElse(null);
|
||||||
|
String value = ((Text)runCj.getValue()).getValue();
|
||||||
|
originEqCode.append(value);
|
||||||
|
U u = Optional.ofNullable(run.getRPr()).filter(f -> Objects.nonNull(f.getU()) && !UnderlineEnumeration.NONE.equals((Object)f.getU().getVal()) && Objects.nonNull(f.getU().getVal())).map(RPrAbstract::getU).orElse(null);
|
||||||
|
if (StringUtils.isBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = value.replace("\u3000", "WPU1WPU").replace(" ", "WPU1WPU");
|
||||||
|
} else if (StringUtils.isNotBlank((CharSequence)value) && Objects.nonNull(u)) {
|
||||||
|
value = "WPU2BEGIN" + value + "WPU2END";
|
||||||
|
} else if (StringUtils.isBlank((CharSequence)value)) {
|
||||||
|
// empty if block
|
||||||
|
}
|
||||||
|
String trimmedOrigin = CtoMathFinder.extractEqPrefix(originEqCode);
|
||||||
|
if (trimmedOrigin.startsWith("eq")) {
|
||||||
|
isEq = true;
|
||||||
|
}
|
||||||
|
if (isEq) {
|
||||||
|
if (CollectionUtils.isNotEmpty(fieldRunIndexs)) {
|
||||||
|
fieldRunIndexs.forEach(m -> paragraph.getContent().set((int)m, factory.createR()));
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(vertAlign)) {
|
||||||
|
String newContent;
|
||||||
|
String tmp;
|
||||||
|
if ("superscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (superScriptFlag && (start = eqCode.lastIndexOf("\\s\\up4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\up4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\up4(" + value + ")";
|
||||||
|
}
|
||||||
|
superScriptFlag = true;
|
||||||
|
subScriptFlag = false;
|
||||||
|
} else if ("subscript".equalsIgnoreCase(vertAlign.getVal().toString())) {
|
||||||
|
int end;
|
||||||
|
int start;
|
||||||
|
if (subScriptFlag && (start = eqCode.lastIndexOf("\\s\\do4(")) != -1 && (end = eqCode.toString().indexOf(41, start)) != -1) {
|
||||||
|
tmp = eqCode.substring(start + 7, end);
|
||||||
|
newContent = tmp + value;
|
||||||
|
eqCode = new StringBuilder(eqCode.substring(0, start));
|
||||||
|
eqCode.append("\\s\\do4(").append(newContent).append(")");
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank((CharSequence)value)) {
|
||||||
|
value = "\\s\\do4(" + value + ")";
|
||||||
|
}
|
||||||
|
subScriptFlag = true;
|
||||||
|
superScriptFlag = false;
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
superScriptFlag = false;
|
||||||
|
subScriptFlag = false;
|
||||||
|
}
|
||||||
|
eqCode.append(value);
|
||||||
|
paragraph.getContent().set(i, factory.createR());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (trimmedOrigin.length() < 2 || "eq".startsWith(trimmedOrigin)) {
|
||||||
|
eqCode.append(value);
|
||||||
|
fieldRunIndexs.add(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
inFiled = false;
|
||||||
|
fieldRunIndexs.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String strToUpperRoman(String str) {
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 1 || num > 3999) {
|
||||||
|
return "I";
|
||||||
|
}
|
||||||
|
int[] values = new int[]{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
|
||||||
|
String[] symbols = new String[]{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
|
||||||
|
StringBuilder roman = new StringBuilder();
|
||||||
|
for (int i = 0; i < values.length; ++i) {
|
||||||
|
while (num >= values[i]) {
|
||||||
|
roman.append(symbols[i]);
|
||||||
|
num -= values[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roman.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToDecimalZero(String str) {
|
||||||
|
if (str.length() == 1) {
|
||||||
|
return "0" + str;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String strToLowerLetter(String str) {
|
||||||
|
int n = Integer.parseInt(str);
|
||||||
|
int length = (n - 1) / 26 + 1;
|
||||||
|
char baseChar = (char)(97 + (n - 1) % 26);
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
sb.append(baseChar);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String strToChineseCounting(String str) {
|
||||||
|
String s;
|
||||||
|
int num = Integer.parseInt(str);
|
||||||
|
if (num < 0 || num >= 1000000000) {
|
||||||
|
throw new IllegalArgumentException("\u4ec5\u652f\u6301 0 \u5230 999,999,999");
|
||||||
|
}
|
||||||
|
if (num == 0) {
|
||||||
|
return "\u3007";
|
||||||
|
}
|
||||||
|
char[] digits = new char[]{'\u3007', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d'};
|
||||||
|
String[] units = new String[]{"", "\u5341", "\u767e", "\u5343"};
|
||||||
|
String[] bigUnits = new String[]{"", "\u4e07", "\u4ebf"};
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
int yi = num / 100000000;
|
||||||
|
int wan = num / 10000 % 10000;
|
||||||
|
int ge = num % 10000;
|
||||||
|
if (yi > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(yi, digits, units)).append(bigUnits[2]);
|
||||||
|
if (wan == 0 && ge > 0) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wan > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(wan, digits, units)).append(bigUnits[1]);
|
||||||
|
if (ge > 0 && ge < 1000) {
|
||||||
|
result.append('\u3007');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ge > 0) {
|
||||||
|
result.append(CtoMathFinder.convertSegment(ge, digits, units));
|
||||||
|
}
|
||||||
|
if ((s = result.toString()).startsWith("\u4e00\u5341")) {
|
||||||
|
s = s.substring(1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String convertSegment(int n, char[] digits, String[] units) {
|
||||||
|
if (n == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int power = 3 - i;
|
||||||
|
int base = (int)Math.pow(10.0, power);
|
||||||
|
int digit = n / base % 10;
|
||||||
|
if (digit != 0) {
|
||||||
|
sb.append(digits[digit]).append(units[power]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sb.length() <= 0 || n % base == 0 || power <= 0) continue;
|
||||||
|
sb.append('\u3007');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getCircledNumber(String num) {
|
||||||
|
int n = Integer.parseInt(num);
|
||||||
|
if (n >= 1 && n <= 20) {
|
||||||
|
return String.valueOf((char)(9312 + n - 1));
|
||||||
|
}
|
||||||
|
if (n >= 21 && n <= 35) {
|
||||||
|
return String.valueOf((char)(12881 + n - 21));
|
||||||
|
}
|
||||||
|
if (n >= 36 && n <= 40) {
|
||||||
|
return String.valueOf((char)(12977 + n - 36));
|
||||||
|
}
|
||||||
|
if (n >= 41 && n <= 50) {
|
||||||
|
return String.valueOf((char)(12982 + n - 41));
|
||||||
|
}
|
||||||
|
log.info("\u83b7\u53d6\u5e26\u5708\u5b57\u7b26\u5931\u8d25 {}", (Object)num);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractEqPrefix(StringBuilder originEqCode) {
|
||||||
|
int i;
|
||||||
|
String s = originEqCode.toString().trim();
|
||||||
|
for (i = 0; i < s.length() && s.charAt(i) == '{'; ++i) {
|
||||||
|
}
|
||||||
|
return s.substring(i).trim().toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
433
java-svc/src/main/java/com/wordparse/core/DrawingPictFinder.java
Normal file
433
java-svc/src/main/java/com/wordparse/core/DrawingPictFinder.java
Normal file
@@ -0,0 +1,433 @@
|
|||||||
|
/*
|
||||||
|
* Decompiled with CFR 0.152.
|
||||||
|
*
|
||||||
|
* Could not load the following classes:
|
||||||
|
* com.alibaba.excel.util.StringUtils
|
||||||
|
* java.util.UUID
|
||||||
|
* javax.xml.bind.JAXBElement
|
||||||
|
* org.apache.commons.collections4.CollectionUtils
|
||||||
|
* org.docx4j.TraversalUtil$CallbackImpl
|
||||||
|
* org.docx4j.dml.GraphicData
|
||||||
|
* org.docx4j.dml.picture.Pic
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Anchor
|
||||||
|
* org.docx4j.dml.wordprocessingDrawing.Inline
|
||||||
|
* org.docx4j.openpackaging.packages.WordprocessingMLPackage
|
||||||
|
* org.docx4j.openpackaging.parts.Part
|
||||||
|
* org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage
|
||||||
|
* org.docx4j.openpackaging.parts.relationships.RelationshipsPart
|
||||||
|
* org.docx4j.vml.CTImageData
|
||||||
|
* org.docx4j.vml.CTShape
|
||||||
|
* org.docx4j.vml.officedrawing.CTOLEObject
|
||||||
|
* org.docx4j.wml.CTObject
|
||||||
|
* org.docx4j.wml.Drawing
|
||||||
|
* org.docx4j.wml.ObjectFactory
|
||||||
|
* org.docx4j.wml.Pict
|
||||||
|
* org.docx4j.wml.R
|
||||||
|
* org.docx4j.wml.Text
|
||||||
|
* org.slf4j.Logger
|
||||||
|
* org.slf4j.LoggerFactory
|
||||||
|
*/
|
||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import com.wordparse.core.ImageBO;
|
||||||
|
import com.wordparse.core.ParseBO;
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import javax.xml.bind.JAXBElement;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.docx4j.TraversalUtil;
|
||||||
|
import org.docx4j.dml.GraphicData;
|
||||||
|
import org.docx4j.dml.picture.Pic;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Anchor;
|
||||||
|
import org.docx4j.dml.wordprocessingDrawing.Inline;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.Part;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
|
||||||
|
import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
|
||||||
|
import org.docx4j.vml.CTImageData;
|
||||||
|
import org.docx4j.vml.CTShape;
|
||||||
|
import org.docx4j.vml.officedrawing.CTOLEObject;
|
||||||
|
import org.docx4j.wml.CTObject;
|
||||||
|
import org.docx4j.wml.Drawing;
|
||||||
|
import org.docx4j.wml.ObjectFactory;
|
||||||
|
import org.docx4j.wml.Pict;
|
||||||
|
import org.docx4j.wml.R;
|
||||||
|
import org.docx4j.wml.Text;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class DrawingPictFinder
|
||||||
|
extends TraversalUtil.CallbackImpl {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(DrawingPictFinder.class);
|
||||||
|
private final WordprocessingMLPackage wordMlPackage;
|
||||||
|
private final ParseBO parseBO;
|
||||||
|
private static final double CROP_SCALE = 1.52587890625;
|
||||||
|
|
||||||
|
public DrawingPictFinder(WordprocessingMLPackage wordMlPackage, ParseBO parseBO) {
|
||||||
|
this.wordMlPackage = wordMlPackage;
|
||||||
|
this.parseBO = parseBO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> apply(Object o) {
|
||||||
|
List contentList;
|
||||||
|
List anyAndAny;
|
||||||
|
R r;
|
||||||
|
if (o instanceof Drawing && ((Drawing)o).getParent() instanceof R) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Drawing drawing = (Drawing)o;
|
||||||
|
List anchorOrInline = drawing.getAnchorOrInline();
|
||||||
|
if (CollectionUtils.isNotEmpty((Collection)anchorOrInline)) {
|
||||||
|
Object anchorOrInlineObj = anchorOrInline.get(0);
|
||||||
|
GraphicData graphicData = null;
|
||||||
|
long cx = 0L;
|
||||||
|
long cy = 0L;
|
||||||
|
if (anchorOrInlineObj instanceof Inline) {
|
||||||
|
Inline inline = (Inline)anchorOrInlineObj;
|
||||||
|
graphicData = inline.getGraphic().getGraphicData();
|
||||||
|
cx = inline.getExtent().getCx();
|
||||||
|
cy = inline.getExtent().getCy();
|
||||||
|
} else if (anchorOrInlineObj instanceof Anchor) {
|
||||||
|
Anchor anchor = (Anchor)anchorOrInlineObj;
|
||||||
|
graphicData = anchor.getGraphic().getGraphicData();
|
||||||
|
cx = anchor.getExtent().getCx();
|
||||||
|
cy = anchor.getExtent().getCy();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(graphicData) && "http://schemas.openxmlformats.org/drawingml/2006/picture".equals(graphicData.getUri())) {
|
||||||
|
for (Object object : graphicData.getAny()) {
|
||||||
|
String path;
|
||||||
|
JAXBElement jaxbElement;
|
||||||
|
if (!(object instanceof JAXBElement) || !((jaxbElement = (JAXBElement)object).getValue() instanceof Pic)) continue;
|
||||||
|
Pic pic = (Pic)jaxbElement.getValue();
|
||||||
|
String imageDataId = null;
|
||||||
|
int cropLeft = 0;
|
||||||
|
int cropTop = 0;
|
||||||
|
int cropRight = 0;
|
||||||
|
int cropBottom = 0;
|
||||||
|
int rotation = 0;
|
||||||
|
if (Objects.nonNull(pic.getBlipFill())) {
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getBlip())) {
|
||||||
|
imageDataId = pic.getBlipFill().getBlip().getEmbed();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getBlipFill().getSrcRect())) {
|
||||||
|
cropLeft = pic.getBlipFill().getSrcRect().getL();
|
||||||
|
cropTop = pic.getBlipFill().getSrcRect().getT();
|
||||||
|
cropRight = pic.getBlipFill().getSrcRect().getR();
|
||||||
|
cropBottom = pic.getBlipFill().getSrcRect().getB();
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(pic.getSpPr()) && Objects.nonNull(pic.getSpPr().getXfrm())) {
|
||||||
|
rotation = pic.getSpPr().getXfrm().getRot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, Object> map = this.dealCropRotate(imageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get("width");
|
||||||
|
Object heightObj = map.get("height");
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
double width = Objects.nonNull(widthObj) ? Double.parseDouble(widthObj.toString()) : (double)Math.round((double)cx / 9525.0);
|
||||||
|
double height = Objects.nonNull(heightObj) ? Double.parseDouble(heightObj.toString()) : (double)Math.round((double)cy / 9525.0);
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u56fe\u7247Part\uff0cimageDataId={}", (Object)imageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(imageDataId);
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(width).setHeight(height).setPictureUrl(path);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
R r2 = (R)drawing.getParent();
|
||||||
|
List contentList2 = r2.getContent();
|
||||||
|
int drawingIndex = contentList2.indexOf(drawing);
|
||||||
|
if (drawingIndex != -1) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
contentList2.set(drawingIndex, text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.warn("\u672a\u627e\u5230drawing\u5bf9\u8c61\u5728\u7236\u5143\u7d20\u4e2d\u7684\u7d22\u5f15\uff0c\u8df3\u8fc7\u66ff\u6362\u64cd\u4f5c");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof Pict && ((Pict)o).getParent() instanceof R) {
|
||||||
|
Pict pict = (Pict)o;
|
||||||
|
r = (R)pict.getParent();
|
||||||
|
anyAndAny = pict.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int pictIndex = contentList.indexOf(pict);
|
||||||
|
if (pictIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, pictIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (o instanceof CTObject && ((CTObject)o).getParent() instanceof R) {
|
||||||
|
CTObject ctObject = (CTObject)o;
|
||||||
|
r = (R)ctObject.getParent();
|
||||||
|
anyAndAny = ctObject.getAnyAndAny();
|
||||||
|
contentList = r.getContent();
|
||||||
|
int ctObjectIndex = contentList.indexOf(ctObject);
|
||||||
|
if (ctObjectIndex != -1) {
|
||||||
|
this.dealCtShape(anyAndAny, r, ctObjectIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractValue(String style, String property) {
|
||||||
|
if (style == null || property == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String regex = "\\b" + Pattern.quote(property) + "\\s*:\\s*([\\d.]+[a-zA-Z]*)";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(style);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dealCtShape(List<Object> anyAndAny, R r, int pictIndex) {
|
||||||
|
String ctOleObjectRid = anyAndAny.stream().filter(f -> f instanceof JAXBElement && ((JAXBElement)f).getValue() instanceof CTOLEObject).map(f -> ((CTOLEObject)((JAXBElement)f).getValue()).getId()).filter(Objects::nonNull).findFirst().orElse(null);
|
||||||
|
if (Objects.nonNull(ctOleObjectRid) && Objects.nonNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
} else if (Objects.isNull(ctOleObjectRid) || Objects.isNull(this.parseBO.getMathTypeMap().get(this.parseBO.getMathTypePlaceholder(ctOleObjectRid)))) {
|
||||||
|
if (Objects.nonNull(ctOleObjectRid)) {
|
||||||
|
this.parseBO.getMathTypeMap().remove(this.parseBO.getMathTypePlaceholder(ctOleObjectRid));
|
||||||
|
}
|
||||||
|
for (Object object : anyAndAny) {
|
||||||
|
String path;
|
||||||
|
double heightPx;
|
||||||
|
double widthPx;
|
||||||
|
if (!(object instanceof JAXBElement) || !(((JAXBElement)object).getValue() instanceof CTShape)) continue;
|
||||||
|
CTShape ctShape = (CTShape)((JAXBElement)object).getValue();
|
||||||
|
String width = this.extractValue(ctShape.getStyle(), "width");
|
||||||
|
String height = this.extractValue(ctShape.getStyle(), "height");
|
||||||
|
CTImageData ctImageData = ctShape.getEGShapeElements().stream().filter(f -> f.getValue() instanceof CTImageData).map(m -> (CTImageData)m.getValue()).findFirst().orElse(null);
|
||||||
|
if (!Objects.nonNull(ctImageData) || !StringUtils.isNotBlank((CharSequence)width) || !StringUtils.isNotBlank((CharSequence)height)) continue;
|
||||||
|
String ctImageDataId = ctImageData.getId();
|
||||||
|
long cx = 0L;
|
||||||
|
try {
|
||||||
|
double widthPtIn = Double.parseDouble(width.substring(0, width.length() - 2));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
cx = Math.round(widthPtIn * 12700.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
cx = Math.round(widthPtIn * 914400.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ignored) {
|
||||||
|
log.error("extractWidth error");
|
||||||
|
}
|
||||||
|
int cropLeft = this.parseVmlCrop(ctImageData.getCropleft());
|
||||||
|
int cropTop = this.parseVmlCrop(ctImageData.getCroptop());
|
||||||
|
int cropRight = this.parseVmlCrop(ctImageData.getCropright());
|
||||||
|
int cropBottom = this.parseVmlCrop(ctImageData.getCropbottom());
|
||||||
|
String rotRaw = this.extractValue(ctShape.getStyle(), "rotation");
|
||||||
|
int rotation = rotRaw == null ? 0 : (int)Math.round((double)Long.parseLong(rotRaw.replaceAll("[^\\d-]", "")) * 0.91552734375);
|
||||||
|
Map<String, Object> map = this.dealCropRotate(ctImageDataId, cx, cropLeft, cropTop, cropRight, cropBottom, rotation);
|
||||||
|
Object widthObj = map.get("width");
|
||||||
|
Object heightObj = map.get("height");
|
||||||
|
Object pathObj = map.get("imagePath");
|
||||||
|
if (Objects.nonNull(widthObj) && Objects.nonNull(heightObj)) {
|
||||||
|
widthPx = (Double)map.get("width");
|
||||||
|
heightPx = (Double)map.get("height");
|
||||||
|
} else {
|
||||||
|
widthPx = Double.parseDouble(width.replaceAll("pt|in", ""));
|
||||||
|
if (width.endsWith("pt")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0 / 72.0);
|
||||||
|
} else if (width.endsWith("in")) {
|
||||||
|
widthPx = Math.round(widthPx * 96.0);
|
||||||
|
}
|
||||||
|
heightPx = Double.parseDouble(height.replaceAll("pt|in", ""));
|
||||||
|
if (height.endsWith("pt")) {
|
||||||
|
heightPx = heightPx * 96.0 / 72.0;
|
||||||
|
} else if (height.endsWith("in")) {
|
||||||
|
heightPx *= 96.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(ctImageDataId);
|
||||||
|
if (Objects.nonNull(pathObj)) {
|
||||||
|
path = String.valueOf(pathObj);
|
||||||
|
} else if (Objects.nonNull(part)) {
|
||||||
|
path = part.getPartName().getURI().getPath();
|
||||||
|
} else {
|
||||||
|
log.warn("\u65e0\u6cd5\u83b7\u53d6\u8001\u7248VML\u56fe\u7247Part\uff0cctImageDataId={}", (Object)ctImageDataId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ImageBO imageBO = new ImageBO().setWidth(widthPx).setHeight(heightPx).setPictureUrl(path);
|
||||||
|
String imagePlaceholder = this.parseBO.getImagePlaceholder(ctImageDataId);
|
||||||
|
this.parseBO.getImageMap().put(imagePlaceholder, imageBO);
|
||||||
|
Text text = new ObjectFactory().createText();
|
||||||
|
text.setValue(imagePlaceholder);
|
||||||
|
r.getContent().set(pictIndex, text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> dealCropRotate(String imageDataId, long cx, int cropLeft, int cropTop, int cropRight, int cropBottom, int rotation) {
|
||||||
|
HashMap<String, Object> result = new HashMap<String, Object>();
|
||||||
|
try {
|
||||||
|
if (Objects.nonNull(imageDataId)) {
|
||||||
|
boolean isRotate;
|
||||||
|
boolean isCrop = cropLeft > 0 || cropTop > 0 || cropRight > 0 || cropBottom > 0;
|
||||||
|
boolean bl = isRotate = rotation != 0;
|
||||||
|
if (isCrop || isRotate) {
|
||||||
|
RelationshipsPart relationshipsPart = this.wordMlPackage.getMainDocumentPart().getRelationshipsPart();
|
||||||
|
Part part = relationshipsPart.getPart(imageDataId);
|
||||||
|
BinaryPartAbstractImage imagePart = (BinaryPartAbstractImage)part;
|
||||||
|
BufferedImage targetImage = ImageIO.read(new ByteArrayInputStream(imagePart.getBytes()));
|
||||||
|
if (isCrop) {
|
||||||
|
targetImage = this.cropImage(targetImage, cropLeft, cropTop, cropRight, cropBottom);
|
||||||
|
}
|
||||||
|
if (isRotate) {
|
||||||
|
targetImage = this.rotateImage(targetImage, rotation, cx, result);
|
||||||
|
}
|
||||||
|
if (Objects.nonNull(targetImage)) {
|
||||||
|
String formatName = this.getFormatName(imagePart);
|
||||||
|
String path = "/word/media/" + UUID.randomUUID().toString() + "." + formatName;
|
||||||
|
ImageIO.write((RenderedImage)targetImage, formatName, new File(this.parseBO.getTempDir() + "/temp" + path));
|
||||||
|
result.put("imagePath", path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
log.error("\u5904\u7406\u56fe\u7247\u88c1\u526a\u4e0e\u65cb\u8f6c\u5f02\u5e38", (Throwable)e);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFormatName(BinaryPartAbstractImage imagePart) {
|
||||||
|
String contentType = imagePart.getContentType();
|
||||||
|
String formatName = contentType.contains("png") ? "png" : (contentType.contains("jpeg") ? "jpeg" : (contentType.contains("gif") ? "gif" : (contentType.contains("bmp") ? "bmp" : "png")));
|
||||||
|
return formatName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage cropImage(BufferedImage originalImage, int srcRectL, int srcRectT, int srcRectR, int srcRectB) {
|
||||||
|
double leftRatio = srcRectL < 0 ? 0.0 : (double)srcRectL / 100000.0;
|
||||||
|
double topRatio = srcRectT < 0 ? 0.0 : (double)srcRectT / 100000.0;
|
||||||
|
double rightRatio = srcRectR < 0 ? 0.0 : (double)srcRectR / 100000.0;
|
||||||
|
double bottomRatio = srcRectB < 0 ? 0.0 : (double)srcRectB / 100000.0;
|
||||||
|
int originalWidth = originalImage.getWidth();
|
||||||
|
int originalHeight = originalImage.getHeight();
|
||||||
|
int left = (int)Math.floor(leftRatio * (double)originalWidth);
|
||||||
|
int top = (int)Math.floor(topRatio * (double)originalHeight);
|
||||||
|
int right = (int)Math.floor(rightRatio * (double)originalWidth);
|
||||||
|
int bottom = (int)Math.floor(bottomRatio * (double)originalHeight);
|
||||||
|
int cropWidth = Math.max(1, originalWidth - left - right);
|
||||||
|
int cropHeight = Math.max(1, originalHeight - top - bottom);
|
||||||
|
if (left < 0 || top < 0 || left + cropWidth > originalWidth || top + cropHeight > originalHeight) {
|
||||||
|
log.error("\u88c1\u526a\u533a\u57df\u65e0\u6548\u6216\u8d85\u51fa\u56fe\u7247\u8fb9\u754c");
|
||||||
|
}
|
||||||
|
return originalImage.getSubimage(left, top, cropWidth, cropHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage rotateImage(BufferedImage originalImage, long rotation, long currentCx, Map<String, Object> result) {
|
||||||
|
int origWidth = originalImage.getWidth();
|
||||||
|
int origHeight = originalImage.getHeight();
|
||||||
|
if (origWidth <= 0 || origHeight <= 0) {
|
||||||
|
log.error("\u56fe\u7247\u533a\u57df\u65e0\u6548");
|
||||||
|
}
|
||||||
|
double emuPerPx = (double)currentCx / (double)origWidth;
|
||||||
|
if (currentCx == 0L) {
|
||||||
|
emuPerPx = 9525.0;
|
||||||
|
}
|
||||||
|
double degrees = (double)rotation / 60000.0;
|
||||||
|
double radians = Math.toRadians(degrees);
|
||||||
|
double sin = Math.abs(Math.sin(radians));
|
||||||
|
double cos = Math.abs(Math.cos(radians));
|
||||||
|
double TOLERANCE = 1.0E-10;
|
||||||
|
if (sin < 1.0E-10) {
|
||||||
|
sin = 0.0;
|
||||||
|
}
|
||||||
|
if (cos < 1.0E-10) {
|
||||||
|
cos = 0.0;
|
||||||
|
}
|
||||||
|
int newWidth = (int)Math.ceil((double)origWidth * cos + (double)origHeight * sin);
|
||||||
|
int newHeight = (int)Math.ceil((double)origWidth * sin + (double)origHeight * cos);
|
||||||
|
newWidth = Math.max(1, newWidth);
|
||||||
|
newHeight = Math.max(1, newHeight);
|
||||||
|
int superSampleFactor = 4;
|
||||||
|
int ssWidth = origWidth * superSampleFactor;
|
||||||
|
int ssHeight = origHeight * superSampleFactor;
|
||||||
|
int ssNewW = newWidth * superSampleFactor;
|
||||||
|
int ssNewH = newHeight * superSampleFactor;
|
||||||
|
int imgType1 = originalImage.getType() == 0 ? 2 : originalImage.getType();
|
||||||
|
BufferedImage highResOriginal = new BufferedImage(ssWidth, ssHeight, imgType1);
|
||||||
|
Graphics2D g2dHighRes = highResOriginal.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dHighRes);
|
||||||
|
g2dHighRes.drawImage(originalImage, 0, 0, ssWidth, ssHeight, null);
|
||||||
|
g2dHighRes.dispose();
|
||||||
|
int imgType2 = highResOriginal.getType() == 0 ? 2 : highResOriginal.getType();
|
||||||
|
BufferedImage rotatedSuperSample = new BufferedImage(ssNewW, ssNewH, imgType2);
|
||||||
|
Graphics2D g2dRotated = rotatedSuperSample.createGraphics();
|
||||||
|
if (highResOriginal.getType() == 2) {
|
||||||
|
g2dRotated.setComposite(AlphaComposite.Clear);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
g2dRotated.setComposite(AlphaComposite.SrcOver);
|
||||||
|
} else {
|
||||||
|
g2dRotated.setColor(Color.WHITE);
|
||||||
|
g2dRotated.fillRect(0, 0, ssNewW, ssNewH);
|
||||||
|
}
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2dRotated.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2dRotated.translate((double)ssNewW / 2.0, (double)ssNewH / 2.0);
|
||||||
|
g2dRotated.rotate(radians);
|
||||||
|
g2dRotated.drawImage((Image)highResOriginal, -ssWidth / 2, -ssHeight / 2, null);
|
||||||
|
g2dRotated.dispose();
|
||||||
|
BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, rotatedSuperSample.getType() == 0 ? 2 : rotatedSuperSample.getType());
|
||||||
|
Graphics2D g2dFinal = rotatedImage.createGraphics();
|
||||||
|
this.configureGraphics2D(g2dFinal);
|
||||||
|
g2dFinal.drawImage(rotatedSuperSample, 0, 0, newWidth, newHeight, null);
|
||||||
|
g2dFinal.dispose();
|
||||||
|
long newCx = Math.round((double)newWidth * emuPerPx);
|
||||||
|
long newCy = Math.round((double)newHeight * emuPerPx);
|
||||||
|
double newWidthPx = Math.round((double)newCx / 9525.0);
|
||||||
|
double newHeightPx = Math.round((double)newCy / 9525.0);
|
||||||
|
result.put("width", newWidthPx);
|
||||||
|
result.put("height", newHeightPx);
|
||||||
|
return rotatedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureGraphics2D(Graphics2D g2d) {
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseVmlCrop(String vmlCrop) {
|
||||||
|
if (vmlCrop == null || StringUtils.isBlank((CharSequence)vmlCrop)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String clean = vmlCrop.trim().replaceAll("[fF]$", "");
|
||||||
|
try {
|
||||||
|
int value = Integer.parseInt(clean);
|
||||||
|
return (int)Math.round((double)value * 1.52587890625);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1025
java-svc/src/main/java/com/wordparse/core/EqToLatexUtil.java
Normal file
1025
java-svc/src/main/java/com/wordparse/core/EqToLatexUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
77
java-svc/src/main/java/com/wordparse/core/HtmlAttr.java
Normal file
77
java-svc/src/main/java/com/wordparse/core/HtmlAttr.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand-neutral HTML attribute names (CSS properties and HTML attributes).
|
||||||
|
*
|
||||||
|
* Derived from jby AttrEnum — all tinymce / brand-specific entries removed.
|
||||||
|
*/
|
||||||
|
public enum HtmlAttr {
|
||||||
|
|
||||||
|
STYLE("style"),
|
||||||
|
COLOR("color"),
|
||||||
|
BACKGROUND_COLOR("background-color"),
|
||||||
|
BACKGROUND("background"),
|
||||||
|
FONT_WEIGHT("font-weight"),
|
||||||
|
FONT_SIZE("font-size"),
|
||||||
|
MIN_HEIGHT("min-height"),
|
||||||
|
LINE_HEIGHT("line-height"),
|
||||||
|
TEXT_ALIGN("text-align"),
|
||||||
|
PADDING("padding"),
|
||||||
|
PADDING_TOP("padding-top"),
|
||||||
|
PADDING_RIGHT("padding-right"),
|
||||||
|
PADDING_BOTTOM("padding-bottom"),
|
||||||
|
PADDING_LEFT("padding-left"),
|
||||||
|
TEXT_INDENT("text-indent"),
|
||||||
|
MARGIN("margin"),
|
||||||
|
MARGIN_TOP("margin-top"),
|
||||||
|
MARGIN_RIGHT("margin-right"),
|
||||||
|
MARGIN_BOTTOM("margin-bottom"),
|
||||||
|
MARGIN_LEFT("margin-left"),
|
||||||
|
VERTICAL_ALIGN("vertical-align"),
|
||||||
|
FONT_FAMILY("font-family"),
|
||||||
|
FONT_STYLE("font-style"),
|
||||||
|
FACE("face"),
|
||||||
|
SIZE("size"),
|
||||||
|
HREF("href"),
|
||||||
|
SRC("src"),
|
||||||
|
WIDTH("width"),
|
||||||
|
HEIGHT("height"),
|
||||||
|
CLASS("class"),
|
||||||
|
BORDER_STYLE("border-style"),
|
||||||
|
COLSPAN("colspan"),
|
||||||
|
ROWSPAN("rowspan"),
|
||||||
|
TEXT_DECORATION("text-decoration"),
|
||||||
|
BACKGROUND_IMAGE("background-image"),
|
||||||
|
TEXT_EMPHASIS_POSITION("text-emphasis-position"),
|
||||||
|
DATA_TYPE("data-type"),
|
||||||
|
SEGMENT("segment"),
|
||||||
|
LIST_STYLE_TYPE("list-style-type"),
|
||||||
|
ALT("alt"),
|
||||||
|
NAME("name"),
|
||||||
|
DATA_HREF("data-href"),
|
||||||
|
DATA_FILE_SUFFIX("data-file-suffix"),
|
||||||
|
BORDER("border"),
|
||||||
|
BORDER_LEFT("border-left"),
|
||||||
|
BORDER_BOTTOM("border-bottom"),
|
||||||
|
BORDER_RIGHT("border-right"),
|
||||||
|
BORDER_TOP("border-top"),
|
||||||
|
BORDER_LEFT_COLOR("border-left-color"),
|
||||||
|
BORDER_BOTTOM_COLOR("border-bottom-color"),
|
||||||
|
BORDER_RIGHT_COLOR("border-right-color"),
|
||||||
|
BORDER_TOP_COLOR("border-top-color");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
HtmlAttr(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
java-svc/src/main/java/com/wordparse/core/HtmlTag.java
Normal file
27
java-svc/src/main/java/com/wordparse/core/HtmlTag.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand-neutral HTML tag names.
|
||||||
|
*
|
||||||
|
* Minimal subset from jby TagEnum — only what the post-processing pipeline needs.
|
||||||
|
*/
|
||||||
|
public enum HtmlTag {
|
||||||
|
|
||||||
|
SUB("sub"),
|
||||||
|
SUP("sup");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
HtmlTag(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
java-svc/src/main/java/com/wordparse/core/ImageBO.java
Normal file
14
java-svc/src/main/java/com/wordparse/core/ImageBO.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
public class ImageBO {
|
||||||
|
private String pictureUrl;
|
||||||
|
private Double width;
|
||||||
|
private Double height;
|
||||||
|
|
||||||
|
public String getPictureUrl() { return pictureUrl; }
|
||||||
|
public Double getWidth() { return width; }
|
||||||
|
public Double getHeight() { return height; }
|
||||||
|
public ImageBO setPictureUrl(String v) { this.pictureUrl = v; return this; }
|
||||||
|
public ImageBO setWidth(Double v) { this.width = v; return this; }
|
||||||
|
public ImageBO setHeight(Double v) { this.height = v; return this; }
|
||||||
|
}
|
||||||
19
java-svc/src/main/java/com/wordparse/core/NumPr.java
Normal file
19
java-svc/src/main/java/com/wordparse/core/NumPr.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class NumPr {
|
||||||
|
private BigInteger start;
|
||||||
|
private BigInteger current;
|
||||||
|
private String numFmt;
|
||||||
|
private String lvlText;
|
||||||
|
|
||||||
|
public BigInteger getStart() { return start; }
|
||||||
|
public BigInteger getCurrent() { return current; }
|
||||||
|
public String getNumFmt() { return numFmt; }
|
||||||
|
public String getLvlText() { return lvlText; }
|
||||||
|
public void setStart(BigInteger v) { this.start = v; }
|
||||||
|
public void setCurrent(BigInteger v) { this.current = v; }
|
||||||
|
public void setNumFmt(String v) { this.numFmt = v; }
|
||||||
|
public void setLvlText(String v) { this.lvlText = v; }
|
||||||
|
}
|
||||||
68
java-svc/src/main/java/com/wordparse/core/ParseBO.java
Normal file
68
java-svc/src/main/java/com/wordparse/core/ParseBO.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class ParseBO {
|
||||||
|
private Path tempDir;
|
||||||
|
private String sourceFilePath;
|
||||||
|
private String htmlPath;
|
||||||
|
private String imagesDir;
|
||||||
|
private String ossFolder;
|
||||||
|
private Transformer transformer;
|
||||||
|
private final Map<String, String> openOfficeXmlMap = new HashMap<>();
|
||||||
|
private final Map<String, String> mathTypeMap = new HashMap<>();
|
||||||
|
private final Map<String, String> eqLatexMap = new HashMap<>();
|
||||||
|
private final Map<String, ImageBO> imageMap = new HashMap<>();
|
||||||
|
private final Map<String, String> imgMap = new HashMap<>();
|
||||||
|
private final Map<String, NumPr> numberingDefinitionsMap = new HashMap<>();
|
||||||
|
private List<JSONObject> latexReplaceList = new ArrayList<>();
|
||||||
|
private int count = 0;
|
||||||
|
private Boolean newParseVersion = false;
|
||||||
|
|
||||||
|
// Placeholder generators
|
||||||
|
public String nextOmmPlchld() { return "WP_OM_" + (++count) + "_END"; }
|
||||||
|
public String nextMtPlchld() { return "WP_MT_" + (++count) + "_END"; }
|
||||||
|
public String nextEqPlchld() { return "WP_EQ_" + (++count) + "_END"; }
|
||||||
|
public String nextImgName() { return "image_" + (++count); }
|
||||||
|
|
||||||
|
// Original named placeholder methods (called by finders)
|
||||||
|
public String getOpenOfficeXmlPlaceholder() { return nextOmmPlchld(); }
|
||||||
|
public String getMathTypePlaceholder(String rid) { return "WP_MT_" + rid + "_END"; }
|
||||||
|
public String getMathTypePlaceholder() { return nextMtPlchld(); }
|
||||||
|
public String getImagePlaceholder(String rid) { return "WP_IMG_" + rid + "_END"; }
|
||||||
|
public String getEqLatexPlaceholder() { return nextEqPlchld(); }
|
||||||
|
public String getShapeImgPlaceholder() { return "WP_IMG_" + (++count) + "_END"; }
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
public Path getTempDir() { return tempDir; }
|
||||||
|
public String getSourceFilePath() { return sourceFilePath; }
|
||||||
|
public String getHtmlPath() { return htmlPath; }
|
||||||
|
public String getImagesDir() { return imagesDir; }
|
||||||
|
public String getOssFolder() { return ossFolder; }
|
||||||
|
public Transformer getTransformer() { return transformer; }
|
||||||
|
public Map<String, String> getOpenOfficeXmlMap() { return openOfficeXmlMap; }
|
||||||
|
public Map<String, String> getMathTypeMap() { return mathTypeMap; }
|
||||||
|
public Map<String, String> getEqLatexMap() { return eqLatexMap; }
|
||||||
|
public Map<String, ImageBO> getImageMap() { return imageMap; }
|
||||||
|
public Map<String, String> getImgMap() { return imgMap; }
|
||||||
|
public Map<String, NumPr> getNumberingDefinitionsMap() { return numberingDefinitionsMap; }
|
||||||
|
public int getCount() { return count; }
|
||||||
|
public Boolean getNewParseVersion() { return newParseVersion; }
|
||||||
|
public List<JSONObject> getLatexReplaceList() { return latexReplaceList; }
|
||||||
|
public Object getStyleDefinitionsPart() { return null; }
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
public ParseBO setTempDir(Path v) { this.tempDir = v; return this; }
|
||||||
|
public ParseBO setSourceFilePath(String v) { this.sourceFilePath = v; return this; }
|
||||||
|
public ParseBO setHtmlPath(String v) { this.htmlPath = v; return this; }
|
||||||
|
public ParseBO setImagesDir(String v) { this.imagesDir = v; return this; }
|
||||||
|
public ParseBO setOssFolder(String v) { this.ossFolder = v; return this; }
|
||||||
|
public ParseBO setTransformer(Transformer v) { this.transformer = v; return this; }
|
||||||
|
public ParseBO setCount(int v) { this.count = v; return this; }
|
||||||
|
public ParseBO setNewParseVersion(Boolean v) { this.newParseVersion = v; return this; }
|
||||||
|
public ParseBO setLatexReplaceList(List<JSONObject> v) { this.latexReplaceList = v; return this; }
|
||||||
|
}
|
||||||
58
java-svc/src/main/java/com/wordparse/core/TagStyleBO.java
Normal file
58
java-svc/src/main/java/com/wordparse/core/TagStyleBO.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package com.wordparse.core;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brand-neutral replacement for jby TagStyleBO.
|
||||||
|
*
|
||||||
|
* Represents a single CSS property (attr, value) parsed from an element's style attribute.
|
||||||
|
*/
|
||||||
|
public class TagStyleBO implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String tagName;
|
||||||
|
private String attr;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public TagStyleBO() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO(String tagName, String attr, String value) {
|
||||||
|
this.tagName = tagName;
|
||||||
|
this.attr = attr;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTagName() {
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setTagName(String tagName) {
|
||||||
|
this.tagName = tagName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttr() {
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setAttr(String attr) {
|
||||||
|
this.attr = attr;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagStyleBO setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TagStyleBO{tagName='" + tagName + "', attr='" + attr + "', value='" + value + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
1496
java-svc/src/main/java/com/wordparse/core/WordParseUtil.java
Normal file
1496
java-svc/src/main/java/com/wordparse/core/WordParseUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
104
java-svc/src/main/java/com/wordparse/oss/OssService.java
Normal file
104
java-svc/src/main/java/com/wordparse/oss/OssService.java
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package com.wordparse.oss;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import software.amazon.awssdk.core.sync.RequestBody;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Client;
|
||||||
|
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||||
|
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
|
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||||
|
import software.amazon.awssdk.regions.Region;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OssService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(OssService.class);
|
||||||
|
|
||||||
|
@Value("${wordparse.oss.endpoint:https://fs.code-lab.cn}")
|
||||||
|
private String endpoint;
|
||||||
|
|
||||||
|
@Value("${wordparse.oss.access-key:}")
|
||||||
|
private String accessKey;
|
||||||
|
|
||||||
|
@Value("${wordparse.oss.secret-key:}")
|
||||||
|
private String secretKey;
|
||||||
|
|
||||||
|
@Value("${wordparse.oss.bucket:word-parse}")
|
||||||
|
private String bucket;
|
||||||
|
|
||||||
|
@Value("${wordparse.oss.prefix:office/file/parse/}")
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
private S3Client client;
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
try {
|
||||||
|
this.client = S3Client.builder()
|
||||||
|
.endpointOverride(URI.create(endpoint))
|
||||||
|
.credentialsProvider(StaticCredentialsProvider.create(
|
||||||
|
AwsBasicCredentials.create(accessKey, secretKey)))
|
||||||
|
.region(Region.US_EAST_1)
|
||||||
|
.forcePathStyle(true)
|
||||||
|
.build();
|
||||||
|
// Quick connectivity test
|
||||||
|
client.headBucket(b -> b.bucket(bucket));
|
||||||
|
log.info("OSS client initialized: {} (bucket: {})", endpoint, bucket);
|
||||||
|
} catch (Exception e) {
|
||||||
|
enabled = false;
|
||||||
|
log.warn("OSS unavailable ({}: {}) — images will not be uploaded", e.getClass().getSimpleName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uploadFile(File file, String folder, String fileName) {
|
||||||
|
if (!enabled) return null;
|
||||||
|
try {
|
||||||
|
String key = prefix + folder + "/" + fileName;
|
||||||
|
String contentType = guessContentType(fileName);
|
||||||
|
client.putObject(
|
||||||
|
PutObjectRequest.builder().bucket(bucket).key(key).contentType(contentType).build(),
|
||||||
|
RequestBody.fromFile(file));
|
||||||
|
return endpoint + "/" + bucket + "/" + key;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Upload failed: {}", fileName, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uploadStream(InputStream is, long size, String folder, String fileName) {
|
||||||
|
if (!enabled) return null;
|
||||||
|
try {
|
||||||
|
String key = prefix + folder + "/" + fileName;
|
||||||
|
String contentType = guessContentType(fileName);
|
||||||
|
client.putObject(
|
||||||
|
PutObjectRequest.builder().bucket(bucket).key(key).contentType(contentType).build(),
|
||||||
|
RequestBody.fromInputStream(is, size));
|
||||||
|
return endpoint + "/" + bucket + "/" + key;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Upload failed: {}", fileName, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String guessContentType(String name) {
|
||||||
|
String ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase();
|
||||||
|
return switch (ext) {
|
||||||
|
case "png" -> "image/png";
|
||||||
|
case "jpg", "jpeg" -> "image/jpeg";
|
||||||
|
case "gif" -> "image/gif";
|
||||||
|
case "svg" -> "image/svg+xml";
|
||||||
|
case "wmf" -> "image/wmf";
|
||||||
|
case "emf" -> "image/emf";
|
||||||
|
case "webp" -> "image/webp";
|
||||||
|
default -> "application/octet-stream";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
851
java-svc/src/main/resources/OMML2MML.xsl
Normal file
851
java-svc/src/main/resources/OMML2MML.xsl
Normal file
@@ -0,0 +1,851 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xsl:stylesheet version="1.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||||
|
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||||
|
xmlns:mml="http://www.w3.org/1998/Math/MathML"
|
||||||
|
xmlns="http://www.w3.org/1998/Math/MathML"
|
||||||
|
exclude-result-prefixes="m w">
|
||||||
|
|
||||||
|
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
|
||||||
|
|
||||||
|
<!-- Root template -->
|
||||||
|
<xsl:template match="/">
|
||||||
|
<xsl:apply-templates select="//m:oMath"/>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Main math element -->
|
||||||
|
<xsl:template match="m:oMath">
|
||||||
|
<math>
|
||||||
|
<xsl:apply-templates select="*"/>
|
||||||
|
</math>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Text run -->
|
||||||
|
<xsl:template match="m:r">
|
||||||
|
<xsl:apply-templates select="m:t"/>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Text content -->
|
||||||
|
<xsl:template match="m:t">
|
||||||
|
<!-- Get style from parent <m:r><m:rPr><m:sty> -->
|
||||||
|
<xsl:variable name="sty" select="../m:rPr/m:sty/@m:val"/>
|
||||||
|
|
||||||
|
<!-- Get Word run properties (w:rPr) - check for italic (w:i) -->
|
||||||
|
<!-- w:rPr is a sibling of m:t (both are children of m:r) -->
|
||||||
|
<!-- CRITICAL: w:i with @w:val="0" or "false" means NOT italic! -->
|
||||||
|
<!-- Only treat as italic if w:i exists AND val is not "0" or "false" -->
|
||||||
|
<xsl:variable name="has_word_italic" select="boolean(../w:rPr/w:i[not(@w:val = '0') and not(@w:val = 'false')])"/>
|
||||||
|
|
||||||
|
<!-- Get math "normal" style (m:nor) - text mode button -->
|
||||||
|
<xsl:variable name="has_math_normal" select="boolean(../m:rPr/m:nor)"/>
|
||||||
|
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="normalize-space(.) = ''">
|
||||||
|
<!-- Skip empty text -->
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="number(.) = number(.)">
|
||||||
|
<mn><xsl:value-of select="normalize-space(.)"/></mn>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Fence delimiters: parentheses, brackets, pipes (with fence="true") -->
|
||||||
|
<xsl:when test=". = '(' or . = ')' or . = '[' or . = ']' or . = '|'">
|
||||||
|
<mo fence="true"><xsl:value-of select="normalize-space(.)"/></mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Plain operators (no fence attribute) -->
|
||||||
|
<!-- NOTE: { and } are converted to markers to distinguish source braces from generated LaTeX braces -->
|
||||||
|
<xsl:when test=". = '{' or . = '}' or . = '+' or . = '-' or . = '=' or . = '<' or . = '>' or . = '±' or . = '≠' or . = '≤' or . = '≥' or . = '×' or . = '÷' or . = '∞'">
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test=". = '{'">__LBRACE__</xsl:when>
|
||||||
|
<xsl:when test=". = '}'">__RBRACE__</xsl:when>
|
||||||
|
<xsl:otherwise><xsl:value-of select="normalize-space(.)"/></xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Mixed operators/delimiters (like ")+" or ")=" or "(A" or "={" or ",}") -->
|
||||||
|
<!-- Check if string contains multiple operator/delimiter characters -->
|
||||||
|
<xsl:when test="string-length(normalize-space(.)) > 1 and (contains(., '(') or contains(., ')') or contains(., '[') or contains(., ']') or contains(., '{') or contains(., '}') or contains(., '+') or contains(., '-') or contains(., '=') or contains(., '<') or contains(., '>'))">
|
||||||
|
<!-- Split into individual characters and classify each one -->
|
||||||
|
<!-- Pass style info so letters get proper mathvariant (e.g., mathvariant="normal" for m:nor text mode) -->
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="normalize-space(.)"/>
|
||||||
|
<xsl:with-param name="math_variant">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$has_word_italic">italic</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'b' or $sty = 'bi'">bold</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'p'">normal</xsl:when>
|
||||||
|
<xsl:when test="$has_math_normal">normal</xsl:when>
|
||||||
|
<xsl:otherwise></xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:with-param>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="contains(., '|')">
|
||||||
|
<!-- Special handling for text containing | (like "x|x", "A|B", or "||") -->
|
||||||
|
<!-- Split by | and create separate elements with fence="true" for absolute values -->
|
||||||
|
<xsl:variable name="before" select="substring-before(., '|')"/>
|
||||||
|
<xsl:variable name="after" select="substring-after(., '|')"/>
|
||||||
|
<!-- Process before part (plain text) -->
|
||||||
|
<xsl:if test="$before != ''">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- If before part contains operators or is multi-char, use split-operators -->
|
||||||
|
<xsl:when test="string-length($before) > 1">
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="$before"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Single character - output as mi -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi><xsl:value-of select="$before"/></mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:if>
|
||||||
|
<!-- Output the | as fence for potential absolute value -->
|
||||||
|
<mo fence="true">|</mo>
|
||||||
|
<!-- Process after part recursively if it contains more | or operators -->
|
||||||
|
<xsl:if test="$after != ''">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- If after part is just |, output as fence -->
|
||||||
|
<xsl:when test="$after = '|'">
|
||||||
|
<mo fence="true">|</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- If after part contains more |, recursively process this m:t content -->
|
||||||
|
<xsl:when test="contains($after, '|')">
|
||||||
|
<xsl:call-template name="process-text-with-pipe">
|
||||||
|
<xsl:with-param name="text" select="$after"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- If after part contains operators, use split-operators -->
|
||||||
|
<xsl:when test="string-length($after) > 1 and (contains($after, '+') or contains($after, '-') or contains($after, '='))">
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="$after"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Single character or simple text - output as mi -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi><xsl:value-of select="$after"/></mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="string-length(normalize-space(.)) = 1 and not(contains('0123456789', .))">
|
||||||
|
<mi>
|
||||||
|
<!-- Apply mathvariant based on style -->
|
||||||
|
<!-- Note: m:sty='i' (italic) is NOT handled here as italic is the default for variables -->
|
||||||
|
<!-- Priority: italic (w:i explicit) > bold > plain (m:sty='p') > normal (m:nor) -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$has_word_italic">
|
||||||
|
<xsl:attribute name="mathvariant">italic</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'b' or $sty = 'bi'">
|
||||||
|
<xsl:attribute name="mathvariant">bold</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'p'">
|
||||||
|
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$has_math_normal">
|
||||||
|
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:value-of select="normalize-space(.)"/>
|
||||||
|
</mi>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi>
|
||||||
|
<!-- Apply mathvariant for multi-character identifiers too -->
|
||||||
|
<!-- Note: m:sty='i' (italic) is NOT handled here as italic is the default for variables -->
|
||||||
|
<!-- Priority: italic (w:i explicit) > bold > plain (m:sty='p') > normal (m:nor) -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$has_word_italic">
|
||||||
|
<xsl:attribute name="mathvariant">italic</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'b' or $sty = 'bi'">
|
||||||
|
<xsl:attribute name="mathvariant">bold</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$sty = 'p'">
|
||||||
|
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$has_math_normal">
|
||||||
|
<xsl:attribute name="mathvariant">normal</xsl:attribute>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:value-of select="normalize-space(.)"/>
|
||||||
|
</mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Superscript -->
|
||||||
|
<xsl:template match="m:sSup">
|
||||||
|
<msup>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</msup>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Subscript -->
|
||||||
|
<xsl:template match="m:sSub">
|
||||||
|
<msub>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
</msub>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Sub-superscript -->
|
||||||
|
<xsl:template match="m:sSubSup">
|
||||||
|
<msubsup>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</msubsup>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Pre-script (left-side subscript/superscript) -->
|
||||||
|
<!-- Used for nuclear notation like _{2}^{3}He -->
|
||||||
|
<xsl:template match="m:sPre">
|
||||||
|
<mmultiscripts>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mprescripts/>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</mmultiscripts>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Fraction -->
|
||||||
|
<!-- m:type values: bar (default), noBar, lin (linear), skw (skewed) -->
|
||||||
|
<xsl:template match="m:f">
|
||||||
|
<xsl:variable name="type" select="m:fPr/m:type/@m:val"/>
|
||||||
|
<mfrac>
|
||||||
|
<!-- Add linethickness="0" for noBar fractions (stacked without bar) -->
|
||||||
|
<xsl:if test="$type = 'noBar'">
|
||||||
|
<xsl:attribute name="linethickness">0</xsl:attribute>
|
||||||
|
</xsl:if>
|
||||||
|
<!-- Add bevelled="true" for linear fractions -->
|
||||||
|
<xsl:if test="$type = 'lin'">
|
||||||
|
<xsl:attribute name="bevelled">true</xsl:attribute>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:apply-templates select="m:num"/>
|
||||||
|
<xsl:apply-templates select="m:den"/>
|
||||||
|
</mfrac>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Radical -->
|
||||||
|
<xsl:template match="m:rad">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="m:deg">
|
||||||
|
<mroot>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:deg"/>
|
||||||
|
</mroot>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<msqrt>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</msqrt>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Parentheses/delimiters -->
|
||||||
|
<xsl:template match="m:d">
|
||||||
|
<mrow>
|
||||||
|
<!-- Opening delimiter: skip if begChr has empty or whitespace-only val -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="not(m:dPr/m:begChr)">
|
||||||
|
<mo fence="true">(</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="normalize-space(m:dPr/m:begChr/@m:val) != ''">
|
||||||
|
<mo fence="true">
|
||||||
|
<xsl:value-of select="m:dPr/m:begChr/@m:val"/>
|
||||||
|
</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- begChr with empty/whitespace val → no opening delimiter -->
|
||||||
|
</xsl:choose>
|
||||||
|
<!-- Handle multiple m:e elements with separators -->
|
||||||
|
<xsl:for-each select="m:e">
|
||||||
|
<xsl:apply-templates select="."/>
|
||||||
|
<!-- Insert separator | between elements (except after the last one) -->
|
||||||
|
<xsl:if test="position() != last()">
|
||||||
|
<mo>|</mo>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:for-each>
|
||||||
|
<!-- Closing delimiter: skip if endChr has empty or whitespace-only val -->
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="not(m:dPr/m:endChr)">
|
||||||
|
<mo fence="true">)</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="normalize-space(m:dPr/m:endChr/@m:val) != ''">
|
||||||
|
<mo fence="true">
|
||||||
|
<xsl:value-of select="m:dPr/m:endChr/@m:val"/>
|
||||||
|
</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- endChr with empty/whitespace val → no closing delimiter -->
|
||||||
|
</xsl:choose>
|
||||||
|
</mrow>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Function -->
|
||||||
|
<xsl:template match="m:func">
|
||||||
|
<mrow>
|
||||||
|
<xsl:apply-templates select="m:fName"/>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mrow>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Matrix -->
|
||||||
|
<xsl:template match="m:m">
|
||||||
|
<mtable>
|
||||||
|
<xsl:apply-templates select="m:mr"/>
|
||||||
|
</mtable>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Matrix row -->
|
||||||
|
<xsl:template match="m:mr">
|
||||||
|
<mtr>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mtr>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Equation array (for piecewise functions) -->
|
||||||
|
<xsl:template match="m:eqArr">
|
||||||
|
<mtable>
|
||||||
|
<!-- Each <m:e> child represents one row in the piecewise function -->
|
||||||
|
<xsl:for-each select="m:e">
|
||||||
|
<mtr>
|
||||||
|
<mtd>
|
||||||
|
<xsl:apply-templates select="node()"/>
|
||||||
|
</mtd>
|
||||||
|
</mtr>
|
||||||
|
</xsl:for-each>
|
||||||
|
</mtable>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Bar (overbar/underbar) -->
|
||||||
|
<!-- OMML spec: no pos attribute = underline (default), pos="top" = overline, pos="bot" = underline -->
|
||||||
|
<xsl:template match="m:bar">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="m:barPr/m:pos/@m:val = 'top'">
|
||||||
|
<mover>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mo>‾</mo>
|
||||||
|
</mover>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<!-- Default: no pos attribute or pos="bot" both mean underline -->
|
||||||
|
<munder>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mo>‾</mo>
|
||||||
|
</munder>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Limits -->
|
||||||
|
<!-- limLow: Lower limit position (content BELOW the base, like underscript) -->
|
||||||
|
<xsl:template match="m:limLow">
|
||||||
|
<munder>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:lim"/>
|
||||||
|
</munder>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- limUpp: Upper limit position (content ABOVE the base, like overscript) -->
|
||||||
|
<xsl:template match="m:limUpp">
|
||||||
|
<mover>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:lim"/>
|
||||||
|
</mover>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template match="m:limUppLow">
|
||||||
|
<munderover>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<xsl:apply-templates select="m:lim[1]"/>
|
||||||
|
<xsl:apply-templates select="m:lim[2]"/>
|
||||||
|
</munderover>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- N-ary operators (integrals, sums, products) -->
|
||||||
|
<xsl:template match="m:nary">
|
||||||
|
<xsl:variable name="chr" select="m:naryPr/m:chr/@m:val"/>
|
||||||
|
<xsl:variable name="limLoc" select="m:naryPr/m:limLoc/@m:val"/>
|
||||||
|
<xsl:variable name="subHide" select="m:naryPr/m:subHide/@m:val"/>
|
||||||
|
<xsl:variable name="supHide" select="m:naryPr/m:supHide/@m:val"/>
|
||||||
|
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Both limits hidden: just operator and integrand -->
|
||||||
|
<xsl:when test="$subHide = '1' and $supHide = '1'">
|
||||||
|
<mrow>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mrow>
|
||||||
|
</xsl:when>
|
||||||
|
|
||||||
|
<!-- Limits as subscript/superscript (default for integrals) -->
|
||||||
|
<xsl:when test="not($limLoc) or $limLoc = 'subSup'">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Both sub and sup present -->
|
||||||
|
<xsl:when test="m:sub and m:sup">
|
||||||
|
<msubsup>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</msubsup>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Only subscript -->
|
||||||
|
<xsl:when test="m:sub">
|
||||||
|
<msub>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
</msub>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Only superscript -->
|
||||||
|
<xsl:when test="m:sup">
|
||||||
|
<msup>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</msup>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- No limits -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mrow>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mrow>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:when>
|
||||||
|
|
||||||
|
<!-- Limits as under/over (for sums, products) -->
|
||||||
|
<xsl:when test="$limLoc = 'undOvr'">
|
||||||
|
<mrow>
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Both under and over -->
|
||||||
|
<xsl:when test="m:sub and m:sup and not($subHide = '1') and not($supHide = '1')">
|
||||||
|
<munderover>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</munderover>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Only under -->
|
||||||
|
<xsl:when test="m:sub and not($subHide = '1')">
|
||||||
|
<munder>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sub"/>
|
||||||
|
</munder>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Only over -->
|
||||||
|
<xsl:when test="m:sup and not($supHide = '1')">
|
||||||
|
<mover>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:sup"/>
|
||||||
|
</mover>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- No visible limits -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>∫</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mrow>
|
||||||
|
</xsl:when>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Accent (vector arrows, dots, etc.) -->
|
||||||
|
<xsl:template match="m:acc">
|
||||||
|
<xsl:variable name="chr" select="m:accPr/m:chr/@m:val"/>
|
||||||
|
<mover>
|
||||||
|
<!-- Add data attribute to distinguish default accents from explicit ones -->
|
||||||
|
<xsl:if test="not($chr)">
|
||||||
|
<xsl:attribute name="data-default-accent">true</xsl:attribute>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>⃗</xsl:otherwise><!-- Default accent (should be hat in most cases) -->
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</mover>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Group character (for over/under arrows with text) -->
|
||||||
|
<xsl:template match="m:groupChr">
|
||||||
|
<xsl:variable name="chr" select="m:groupChrPr/m:chr/@m:val"/>
|
||||||
|
<xsl:variable name="pos" select="m:groupChrPr/m:pos/@m:val"/>
|
||||||
|
<xsl:variable name="vertJc" select="m:groupChrPr/m:vertJc/@m:val"/>
|
||||||
|
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- vertJc='bot': Character at bottom (base), content above → mover(char, content) -->
|
||||||
|
<!-- This is used for extensible arrows like \xrightarrow[]{text} or \overset{text}{arrow} -->
|
||||||
|
<xsl:when test="$vertJc = 'bot'">
|
||||||
|
<mover accent="false">
|
||||||
|
<mo stretchy="true">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>→</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</mover>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- pos='bot': Character position at bottom, content above → munder -->
|
||||||
|
<xsl:when test="$pos = 'bot'">
|
||||||
|
<munder>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>⏟</xsl:otherwise><!-- Default underbrace -->
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</munder>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- pos='top' (or default): Character above content → mover(content, char) -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mover>
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$chr"><xsl:value-of select="$chr"/></xsl:when>
|
||||||
|
<xsl:otherwise>⏞</xsl:otherwise><!-- Default overbrace -->
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</mover>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Box (but detect if it contains groupChr for reaction arrows) -->
|
||||||
|
<xsl:template match="m:box">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Skip boxes with opEmu="1" (operator emulation - should be transparent) -->
|
||||||
|
<!-- This means process all children in m:e transparently -->
|
||||||
|
<xsl:when test="m:boxPr/m:opEmu/@m:val='1'">
|
||||||
|
<xsl:apply-templates select="m:e/*"/>
|
||||||
|
</xsl:when>
|
||||||
|
|
||||||
|
<!-- If box contains ONLY a single direct groupChr, handle specially -->
|
||||||
|
<!-- This is for cases like reaction arrows: text above/below extensible arrow -->
|
||||||
|
<xsl:when test="count(m:e/*) = 1 and m:e/m:groupChr">
|
||||||
|
<xsl:variable name="groupChr" select="m:e/m:groupChr"/>
|
||||||
|
<xsl:variable name="arrow" select="$groupChr/m:groupChrPr/m:chr/@m:val"/>
|
||||||
|
<xsl:variable name="vertJc" select="$groupChr/m:groupChrPr/m:vertJc/@m:val"/>
|
||||||
|
<xsl:variable name="pos" select="$groupChr/m:groupChrPr/m:pos/@m:val"/>
|
||||||
|
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$vertJc = 'bot'">
|
||||||
|
<!-- Arrow at bottom with text above (like \xrightarrow[]{text}) -->
|
||||||
|
<!-- For extensible arrows: mover with arrow base, text above -->
|
||||||
|
<mover accent="false">
|
||||||
|
<mo stretchy="true">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||||||
|
<xsl:otherwise>→</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
<xsl:apply-templates select="$groupChr/m:e"/>
|
||||||
|
</mover>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$pos = 'top'">
|
||||||
|
<!-- Arrow at top: \overrightarrow pattern -->
|
||||||
|
<!-- mover with content base, arrow above -->
|
||||||
|
<mover>
|
||||||
|
<xsl:apply-templates select="$groupChr/m:e"/>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||||||
|
<xsl:otherwise>→</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</mover>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<!-- Default: Vector arrow over content -->
|
||||||
|
<mover>
|
||||||
|
<xsl:apply-templates select="$groupChr/m:e"/>
|
||||||
|
<mo>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="$arrow"><xsl:value-of select="$arrow"/></xsl:when>
|
||||||
|
<xsl:otherwise>→</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</mo>
|
||||||
|
</mover>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:when>
|
||||||
|
|
||||||
|
<!-- Skip phantom/layout boxes (argSz with negative value) -->
|
||||||
|
<!-- These are used for sizing/spacing control, not actual visible boxes -->
|
||||||
|
<xsl:when test="m:e/m:argPr/m:argSz/@m:val < 0">
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</xsl:when>
|
||||||
|
|
||||||
|
<!-- Regular box -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<menclose notation="box">
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</menclose>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Border box with strikethrough and border notation support -->
|
||||||
|
<xsl:template match="m:borderBox">
|
||||||
|
<xsl:variable name="props" select="m:borderBoxPr"/>
|
||||||
|
|
||||||
|
<!-- Check for strikethrough properties -->
|
||||||
|
<xsl:variable name="strikeBLTR" select="boolean($props/m:strikeBLTR)"/>
|
||||||
|
<xsl:variable name="strikeTLBR" select="boolean($props/m:strikeTLBR)"/>
|
||||||
|
<xsl:variable name="strikeH" select="boolean($props/m:strikeH)"/>
|
||||||
|
<xsl:variable name="strikeV" select="boolean($props/m:strikeV)"/>
|
||||||
|
|
||||||
|
<!-- Check for border hiding properties -->
|
||||||
|
<xsl:variable name="hideTop" select="boolean($props/m:hideTop)"/>
|
||||||
|
<xsl:variable name="hideBot" select="boolean($props/m:hideBot)"/>
|
||||||
|
<xsl:variable name="hideLeft" select="boolean($props/m:hideLeft)"/>
|
||||||
|
<xsl:variable name="hideRight" select="boolean($props/m:hideRight)"/>
|
||||||
|
|
||||||
|
<!-- Determine notation attribute -->
|
||||||
|
<xsl:variable name="notation">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Combined diagonal strikes → xcancel -->
|
||||||
|
<xsl:when test="$strikeBLTR and $strikeTLBR">updiagonalstrike downdiagonalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Single diagonal strike BLTR → cancel (updiagonal) -->
|
||||||
|
<xsl:when test="$strikeBLTR">updiagonalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Single diagonal strike TLBR → bcancel (downdiagonal) -->
|
||||||
|
<xsl:when test="$strikeTLBR">downdiagonalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Combined horizontal and vertical → cancel -->
|
||||||
|
<xsl:when test="$strikeH and $strikeV">horizontalstrike verticalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Single horizontal strike → cancel -->
|
||||||
|
<xsl:when test="$strikeH">horizontalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Single vertical strike → bcancel -->
|
||||||
|
<xsl:when test="$strikeV">verticalstrike</xsl:when>
|
||||||
|
|
||||||
|
<!-- Single side visible -->
|
||||||
|
<xsl:when test="not($hideTop) and $hideBot and $hideLeft and $hideRight">top</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and not($hideBot) and $hideLeft and $hideRight">bottom</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and $hideBot and not($hideLeft) and $hideRight">left</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and $hideBot and $hideLeft and not($hideRight)">right</xsl:when>
|
||||||
|
|
||||||
|
<!-- Two sides visible -->
|
||||||
|
<xsl:when test="not($hideTop) and not($hideBot) and $hideLeft and $hideRight">top bottom</xsl:when>
|
||||||
|
<xsl:when test="not($hideTop) and $hideBot and not($hideLeft) and $hideRight">top left</xsl:when>
|
||||||
|
<xsl:when test="not($hideTop) and $hideBot and $hideLeft and not($hideRight)">top right</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and not($hideBot) and not($hideLeft) and $hideRight">bottom left</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and not($hideBot) and $hideLeft and not($hideRight)">bottom right</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and $hideBot and not($hideLeft) and not($hideRight)">left right</xsl:when>
|
||||||
|
|
||||||
|
<!-- Three sides visible -->
|
||||||
|
<xsl:when test="not($hideTop) and not($hideBot) and not($hideLeft) and $hideRight">top bottom left</xsl:when>
|
||||||
|
<xsl:when test="not($hideTop) and not($hideBot) and $hideLeft and not($hideRight)">top bottom right</xsl:when>
|
||||||
|
<xsl:when test="not($hideTop) and $hideBot and not($hideLeft) and not($hideRight)">top left right</xsl:when>
|
||||||
|
<xsl:when test="$hideTop and not($hideBot) and not($hideLeft) and not($hideRight)">bottom left right</xsl:when>
|
||||||
|
|
||||||
|
<!-- All four sides visible (or none hidden) -->
|
||||||
|
<xsl:when test="not($hideTop or $hideBot or $hideLeft or $hideRight)">box</xsl:when>
|
||||||
|
|
||||||
|
<!-- Default: roundedbox -->
|
||||||
|
<xsl:otherwise>roundedbox</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:variable>
|
||||||
|
|
||||||
|
<menclose notation="{$notation}">
|
||||||
|
<xsl:apply-templates select="m:e"/>
|
||||||
|
</menclose>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Math element base -->
|
||||||
|
<xsl:template match="m:e | m:num | m:den | m:sup | m:sub | m:deg | m:fName | m:lim">
|
||||||
|
<mrow>
|
||||||
|
<xsl:apply-templates select="*"/>
|
||||||
|
</mrow>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Template to split mixed operator/delimiter strings character by character -->
|
||||||
|
<!-- Recursive template to process text containing | characters -->
|
||||||
|
<xsl:template name="process-text-with-pipe">
|
||||||
|
<xsl:param name="text"/>
|
||||||
|
<xsl:if test="string-length($text) > 0">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- If text contains |, split and recurse -->
|
||||||
|
<xsl:when test="contains($text, '|')">
|
||||||
|
<xsl:variable name="before" select="substring-before($text, '|')"/>
|
||||||
|
<xsl:variable name="after" select="substring-after($text, '|')"/>
|
||||||
|
<!-- Output before part if not empty -->
|
||||||
|
<xsl:if test="$before != ''">
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- If before is single char, output as mi or mn -->
|
||||||
|
<xsl:when test="string-length($before) = 1">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="contains('0123456789', $before)">
|
||||||
|
<mn><xsl:value-of select="$before"/></mn>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi><xsl:value-of select="$before"/></mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Multi-char - use split-operators -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="$before"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:if>
|
||||||
|
<!-- Output | as fence -->
|
||||||
|
<mo fence="true">|</mo>
|
||||||
|
<!-- Recurse on after part -->
|
||||||
|
<xsl:if test="$after != ''">
|
||||||
|
<xsl:call-template name="process-text-with-pipe">
|
||||||
|
<xsl:with-param name="text" select="$after"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- No more | in text - output as-is -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Single char -->
|
||||||
|
<xsl:when test="string-length($text) = 1">
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="contains('0123456789', $text)">
|
||||||
|
<mn><xsl:value-of select="$text"/></mn>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi><xsl:value-of select="$text"/></mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Multi-char - use split-operators for proper handling -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="$text"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template name="split-operators">
|
||||||
|
<xsl:param name="text"/>
|
||||||
|
<!-- Style variant to apply to identifier (letter) elements: 'normal', 'bold', 'italic', or empty -->
|
||||||
|
<xsl:param name="math_variant" select="''"/>
|
||||||
|
<xsl:if test="string-length($text) > 0">
|
||||||
|
<!-- Process first character -->
|
||||||
|
<xsl:variable name="char" select="substring($text, 1, 1)"/>
|
||||||
|
<xsl:choose>
|
||||||
|
<!-- Fence delimiters with fence="true" -->
|
||||||
|
<xsl:when test="$char = '(' or $char = ')' or $char = '[' or $char = ']' or $char = '|'">
|
||||||
|
<mo fence="true"><xsl:value-of select="$char"/></mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Plain operators (no fence attribute) -->
|
||||||
|
<!-- NOTE: { and } are converted to markers to distinguish source braces from generated LaTeX braces -->
|
||||||
|
<xsl:when test="$char = '+' or $char = '-' or $char = '=' or $char = '<' or $char = '>' or $char = '*' or $char = '/' or $char = '±' or $char = '≠' or $char = '≤' or $char = '≥' or $char = '×' or $char = '÷'">
|
||||||
|
<mo><xsl:value-of select="$char"/></mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Source braces - use markers -->
|
||||||
|
<xsl:when test="$char = '{'">
|
||||||
|
<mo>__LBRACE__</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:when test="$char = '}'">
|
||||||
|
<mo>__RBRACE__</mo>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Numbers -->
|
||||||
|
<xsl:when test="contains('0123456789', $char)">
|
||||||
|
<mn><xsl:value-of select="$char"/></mn>
|
||||||
|
</xsl:when>
|
||||||
|
<!-- Identifiers (letters) -->
|
||||||
|
<xsl:otherwise>
|
||||||
|
<mi>
|
||||||
|
<!-- Apply mathvariant from parent run style if available -->
|
||||||
|
<xsl:if test="$math_variant != ''">
|
||||||
|
<xsl:attribute name="mathvariant"><xsl:value-of select="$math_variant"/></xsl:attribute>
|
||||||
|
</xsl:if>
|
||||||
|
<xsl:value-of select="$char"/>
|
||||||
|
</mi>
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
<!-- Recursively process remaining characters -->
|
||||||
|
<xsl:if test="string-length($text) > 1">
|
||||||
|
<xsl:call-template name="split-operators">
|
||||||
|
<xsl:with-param name="text" select="substring($text, 2)"/>
|
||||||
|
<xsl:with-param name="math_variant" select="$math_variant"/>
|
||||||
|
</xsl:call-template>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:if>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Properties elements - ignore -->
|
||||||
|
<xsl:template match="m:rPr | m:ctrlPr | m:dPr | m:fPr | m:funcPr | m:limLowPr | m:limUppPr | m:naryPr | m:phantPr | m:radPr | m:sPrePr | m:sSubPr | m:sSubSupPr | m:sSupPr | m:accPr | m:barPr | m:boxPr | m:borderBoxPr | m:dPr | m:eqArrPr | m:groupChrPr | m:mPr | w:rPr">
|
||||||
|
<!-- Ignore formatting properties -->
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Control properties - ignore -->
|
||||||
|
<xsl:template match="m:ctrlPr">
|
||||||
|
<!-- Ignore control properties -->
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<!-- Default template -->
|
||||||
|
<xsl:template match="*">
|
||||||
|
<xsl:apply-templates select="*"/>
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
</xsl:stylesheet>
|
||||||
22
java-svc/src/main/resources/application.yml.example
Normal file
22
java-svc/src/main/resources/application.yml.example
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: word-file-parse
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 100MB
|
||||||
|
max-request-size: 100MB
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
wordparse:
|
||||||
|
tmp-path: ${java.io.tmpdir}
|
||||||
|
image-scaling-factor: 2.0
|
||||||
|
transform-scripts-path: /opt/transform
|
||||||
|
python-command: python3
|
||||||
|
oss:
|
||||||
|
endpoint: https://fs.code-lab.cn
|
||||||
|
access-key: ${WORDPARSE_OSS_AK:}
|
||||||
|
secret-key: ${WORDPARSE_OSS_SK:}
|
||||||
|
bucket: word-parse
|
||||||
|
prefix: office/file/parse/
|
||||||
2
java-svc/src/main/resources/static/symbols/E0FC.svg
Normal file
2
java-svc/src/main/resources/static/symbols/E0FC.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.385ex" height="2.262ex" viewBox="0 -750 612 1000" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style=""><defs><path id="MJX-39-TEX-N-2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path><path id="MJX-39-TEX-N-5B" d="M118 -250V750H255V710H158V-210H255V-250H118Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"><g data-mml-node="math"><g data-mml-node="mo"><use xlink:href="#MJX-39-TEX-N-2212"></use></g><g data-mml-node="mspace" transform="translate(778, 0)"></g><g data-mml-node="mo" transform="translate(334, 0)"><use xlink:href="#MJX-39-TEX-N-5B"></use></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 789 B |
2
java-svc/src/main/resources/static/symbols/E100.svg
Normal file
2
java-svc/src/main/resources/static/symbols/E100.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2.204ex" height="2.262ex" viewBox="0 -750 974.3 1000" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style=""><defs><path id="MJX-94-TEX-N-2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path><path id="MJX-94-TEX-N-5D" d="M22 710V750H159V-250H22V-210H119V710H22Z"></path><path id="MJX-94-TEX-I-1D45B" d="M21 287Q22 293 24 303T36 341T56 388T89 425T135 442Q171 442 195 424T225 390T231 369Q231 367 232 367L243 378Q304 442 382 442Q436 442 469 415T503 336T465 179T427 52Q427 26 444 26Q450 26 453 27Q482 32 505 65T540 145Q542 153 560 153Q580 153 580 145Q580 144 576 130Q568 101 554 73T508 17T439 -10Q392 -10 371 17T350 73Q350 92 386 193T423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 180T152 343Q153 348 153 366Q153 405 129 405Q91 405 66 305Q60 285 60 284Q58 278 41 278H27Q21 284 21 287Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"><g data-mml-node="math"><g data-mml-node="mo"><use xlink:href="#MJX-94-TEX-N-2212"></use></g><g data-mml-node="mspace" transform="translate(778, 0)"></g><g data-mml-node="msub" transform="translate(222, 0)"><g data-mml-node="mo"><use xlink:href="#MJX-94-TEX-N-5D"></use></g><g data-mml-node="mi" transform="translate(278, -150) scale(0.707)"><use xlink:href="#MJX-94-TEX-I-1D45B"></use></g></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
2
java-svc/src/main/resources/static/symbols/E101.svg
Normal file
2
java-svc/src/main/resources/static/symbols/E101.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="2.649ex" height="2.262ex" viewBox="0 -750 1170.8 1000" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style=""><defs><path id="MJX-92-TEX-N-2212" d="M84 237T84 250T98 270H679Q694 262 694 250T679 230H98Q84 237 84 250Z"></path><path id="MJX-92-TEX-N-5D" d="M22 710V750H159V-250H22V-210H119V710H22Z"></path><path id="MJX-92-TEX-I-1D45A" d="M21 287Q22 293 24 303T36 341T56 388T88 425T132 442T175 435T205 417T221 395T229 376L231 369Q231 367 232 367L243 378Q303 442 384 442Q401 442 415 440T441 433T460 423T475 411T485 398T493 385T497 373T500 364T502 357L510 367Q573 442 659 442Q713 442 746 415T780 336Q780 285 742 178T704 50Q705 36 709 31T724 26Q752 26 776 56T815 138Q818 149 821 151T837 153Q857 153 857 145Q857 144 853 130Q845 101 831 73T785 17T716 -10Q669 -10 648 17T627 73Q627 92 663 193T700 345Q700 404 656 404H651Q565 404 506 303L499 291L466 157Q433 26 428 16Q415 -11 385 -11Q372 -11 364 -4T353 8T350 18Q350 29 384 161L420 307Q423 322 423 345Q423 404 379 404H374Q288 404 229 303L222 291L189 157Q156 26 151 16Q138 -11 108 -11Q95 -11 87 -5T76 7T74 17Q74 30 112 181Q151 335 151 342Q154 357 154 369Q154 405 129 405Q107 405 92 377T69 316T57 280Q55 278 41 278H27Q21 284 21 287Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="matrix(1 0 0 -1 0 0)"><g data-mml-node="math"><g data-mml-node="mo"><use xlink:href="#MJX-92-TEX-N-2212"></use></g><g data-mml-node="mspace" transform="translate(778, 0)"></g><g data-mml-node="msub" transform="translate(222, 0)"><g data-mml-node="mo"><use xlink:href="#MJX-92-TEX-N-5D"></use></g><g data-mml-node="mi" transform="translate(278, -150) scale(0.707)"><use xlink:href="#MJX-92-TEX-I-1D45A"></use></g></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
60
scripts/mathtype2mathml/.dockerignore
Normal file
60
scripts/mathtype2mathml/.dockerignore
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Node.js
|
||||||
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Ruby
|
||||||
|
.bundle
|
||||||
|
vendor/bundle
|
||||||
|
|
||||||
|
# 开发工具
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
!SETUP_GUIDE.md
|
||||||
|
|
||||||
|
# 临时文件和缓存
|
||||||
|
uploads/*
|
||||||
|
!uploads/.gitkeep
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
logs
|
||||||
|
|
||||||
|
# 编辑器文件
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# 测试文件
|
||||||
|
test_*
|
||||||
|
spec/
|
||||||
|
.rspec
|
||||||
|
|
||||||
|
# 环境文件
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# 构建文件
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# 操作系统文件
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
116
scripts/mathtype2mathml/DOCKER_SUMMARY.md
Normal file
116
scripts/mathtype2mathml/DOCKER_SUMMARY.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# 🐳 MathType to MathML Docker 化完成
|
||||||
|
|
||||||
|
## 📁 新增的 Docker 文件
|
||||||
|
|
||||||
|
| 文件名 | 用途 | 描述 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `Dockerfile` | 容器构建配置 | 定义如何构建包含 Ruby + Node.js 的容器镜像 |
|
||||||
|
| `docker-compose.yml` | 服务编排 | 简化容器启动、端口映射和卷挂载 |
|
||||||
|
| `.dockerignore` | 构建排除 | 指定哪些文件不需要复制到容器中 |
|
||||||
|
| `entrypoint.sh` | 启动脚本 | 容器启动时的环境检查和初始化 |
|
||||||
|
| `docker-build.sh` | 构建脚本 | 自动化构建和启动流程 |
|
||||||
|
| `README.Docker.md` | 详细文档 | 完整的 Docker 部署和使用指南 |
|
||||||
|
| `uploads/.gitkeep` | 目录占位 | 确保上传目录存在 |
|
||||||
|
|
||||||
|
## 🚀 快速启动(3种方式)
|
||||||
|
|
||||||
|
### 方式1:一键脚本(最简单)
|
||||||
|
```bash
|
||||||
|
./docker-build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式2:Docker Compose(推荐)
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式3:原生 Docker
|
||||||
|
```bash
|
||||||
|
docker build -t mathtype-converter .
|
||||||
|
docker run -d --name mathtype-to-mathml -p 3000:3000 -v $(pwd)/uploads:/app/uploads mathtype-converter
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 访问服务
|
||||||
|
|
||||||
|
启动后通过以下地址访问:
|
||||||
|
|
||||||
|
- **Web 界面**: http://localhost:3000/mathtype_converter_demo.html
|
||||||
|
- **健康检查**: http://localhost:3000/health
|
||||||
|
- **API 转换**: `POST http://localhost:3000/convert-mathtype`
|
||||||
|
|
||||||
|
## 📋 常用管理命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看运行状态
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 停止服务
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
docker-compose restart
|
||||||
|
|
||||||
|
# 重建镜像
|
||||||
|
docker-compose build --no-cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 技术栈
|
||||||
|
|
||||||
|
- **基础镜像**: Ruby 3.1 (Debian Bullseye)
|
||||||
|
- **运行时**: Ruby + Node.js LTS
|
||||||
|
- **端口**: 3000
|
||||||
|
- **依赖**: mathtype_to_mathml gem + Express.js
|
||||||
|
- **持久化**: uploads 目录挂载
|
||||||
|
|
||||||
|
## 📊 资源使用
|
||||||
|
|
||||||
|
- **镜像大小**: ~500-800MB
|
||||||
|
- **运行内存**: ~100-200MB
|
||||||
|
- **文件上传限制**: 10MB
|
||||||
|
- **并发处理**: 支持多文件并发转换
|
||||||
|
|
||||||
|
## 🚀 部署到生产环境
|
||||||
|
|
||||||
|
1. **修改端口映射**:编辑 `docker-compose.yml` 中的端口配置
|
||||||
|
2. **添加反向代理**:使用 Nginx/Apache 代理到容器端口
|
||||||
|
3. **配置域名**:更新 Traefik 标签或反向代理配置
|
||||||
|
4. **监控日志**:设置日志轮转和监控告警
|
||||||
|
5. **备份数据**:定期备份 uploads 目录
|
||||||
|
|
||||||
|
## 🐛 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **端口冲突**: 修改 `docker-compose.yml` 中的端口映射
|
||||||
|
2. **Ruby Gem 错误**: 重建镜像 `docker-compose build --no-cache`
|
||||||
|
3. **文件权限**: 检查 uploads 目录权限
|
||||||
|
4. **内存不足**: 增加 Docker 内存限制
|
||||||
|
|
||||||
|
### 调试命令
|
||||||
|
```bash
|
||||||
|
# 进入容器调试
|
||||||
|
docker-compose exec mathtype-converter bash
|
||||||
|
|
||||||
|
# 查看详细日志
|
||||||
|
docker-compose logs --tail=100 mathtype-converter
|
||||||
|
|
||||||
|
# 检查容器内文件
|
||||||
|
docker exec mathtype-to-mathml ls -la /app
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ 验证部署
|
||||||
|
|
||||||
|
部署完成后,验证以下功能:
|
||||||
|
|
||||||
|
1. ✅ 健康检查响应正常
|
||||||
|
2. ✅ Web 界面可访问
|
||||||
|
3. ✅ 文件上传功能正常
|
||||||
|
4. ✅ MathML 转换功能正常
|
||||||
|
5. ✅ 容器重启后服务恢复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
🎉 **Docker 化完成!** 现在您可以轻松地将此服务部署到任何支持 Docker 的平台上。
|
||||||
71
scripts/mathtype2mathml/Dockerfile
Normal file
71
scripts/mathtype2mathml/Dockerfile
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# 使用官方 Ruby 镜像作为基础镜像
|
||||||
|
FROM ruby:3.1-bullseye
|
||||||
|
|
||||||
|
# 设置镜像源(提高国内网络访问速度)
|
||||||
|
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list \
|
||||||
|
&& sed -i 's/security.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# 安装系统依赖
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
gnupg \
|
||||||
|
build-essential \
|
||||||
|
libxml2-dev \
|
||||||
|
libxslt1-dev \
|
||||||
|
pkg-config \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 安装 Node.js 16 (LTS) - 使用官方二进制包,增加重试机制
|
||||||
|
RUN for i in 1 2 3; do \
|
||||||
|
curl -fsSL --connect-timeout 10 --max-time 300 \
|
||||||
|
https://nodejs.org/dist/v16.20.2/node-v16.20.2-linux-x64.tar.xz -o node.tar.xz \
|
||||||
|
&& break || sleep 5; \
|
||||||
|
done \
|
||||||
|
&& tar -xJf node.tar.xz -C /usr/local --strip-components=1 \
|
||||||
|
&& rm node.tar.xz \
|
||||||
|
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 vendor 目录(包含本地gems)
|
||||||
|
COPY vendor ./vendor
|
||||||
|
|
||||||
|
# 复制 Gemfile 和 package.json 先安装依赖(利用 Docker 缓存)
|
||||||
|
COPY Gemfile Gemfile.lock ./
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
# 配置npm使用国内镜像源(提高网络访问速度)
|
||||||
|
RUN npm config set registry https://registry.npmmirror.com/ \
|
||||||
|
&& npm config set timeout 60000 \
|
||||||
|
&& npm config set network-timeout 300000
|
||||||
|
|
||||||
|
# 配置bundle使用国内gem源
|
||||||
|
RUN bundle config mirror.https://rubygems.org https://gems.ruby-china.com
|
||||||
|
|
||||||
|
# 安装 Ruby 依赖(不使用 deployment 模式避免版本冲突)
|
||||||
|
RUN bundle config set --local without 'development test' \
|
||||||
|
&& bundle install --retry=3
|
||||||
|
|
||||||
|
# 安装 Node.js 依赖(使用重试机制)
|
||||||
|
RUN npm install --production --retry=3 || \
|
||||||
|
(npm config set registry https://registry.npmjs.org/ && npm install --production --retry=3)
|
||||||
|
|
||||||
|
# 复制项目文件
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 创建必要的目录
|
||||||
|
RUN mkdir -p uploads public
|
||||||
|
|
||||||
|
# 设置文件权限
|
||||||
|
RUN chmod +x entrypoint.sh
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 设置启动脚本
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|
||||||
|
# 默认命令
|
||||||
|
CMD ["npm", "start"]
|
||||||
21
scripts/mathtype2mathml/Gemfile
Normal file
21
scripts/mathtype2mathml/Gemfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
# Use local, vendored gems
|
||||||
|
gem 'mathtype_to_mathml', path: 'vendor/gems/mathtype_to_mathml'
|
||||||
|
gem 'mathtype', path: 'vendor/gems/mathtype'
|
||||||
|
|
||||||
|
# Required dependency for XML processing
|
||||||
|
gem 'nokogiri', '~> 1.13'
|
||||||
|
|
||||||
|
# Required dependency for mathtype_to_mathml gem
|
||||||
|
gem 'pry'
|
||||||
|
|
||||||
|
# Concurrency support for batch processing
|
||||||
|
gem 'concurrent-ruby', '~> 1.1'
|
||||||
|
|
||||||
|
# Multi-process parallel processing
|
||||||
|
gem 'parallel'
|
||||||
|
|
||||||
|
# Optional: Add these for better development experience
|
||||||
|
gem 'rake', '~> 13.0'
|
||||||
|
gem 'rspec', '~> 3.0' if ENV['RAILS_ENV'] == 'test'
|
||||||
67
scripts/mathtype2mathml/Gemfile.lock
Normal file
67
scripts/mathtype2mathml/Gemfile.lock
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
PATH
|
||||||
|
remote: vendor/gems/mathtype_to_mathml
|
||||||
|
specs:
|
||||||
|
mathtype_to_mathml (0.0.7)
|
||||||
|
mathtype (~> 0.1.0)
|
||||||
|
nokogiri (~> 1.6)
|
||||||
|
|
||||||
|
PATH
|
||||||
|
remote: vendor/gems/mathtype
|
||||||
|
specs:
|
||||||
|
mathtype (0.1.0)
|
||||||
|
bindata (~> 2.1)
|
||||||
|
nokogiri (~> 1.6)
|
||||||
|
ruby-ole (~> 1.2)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
bindata (2.5.1)
|
||||||
|
coderay (1.1.3)
|
||||||
|
concurrent-ruby (1.3.7)
|
||||||
|
io-console (0.8.2)
|
||||||
|
method_source (1.1.0)
|
||||||
|
nokogiri (1.19.4-x64-mingw-ucrt)
|
||||||
|
racc (~> 1.4)
|
||||||
|
parallel (2.1.0)
|
||||||
|
pry (0.16.0)
|
||||||
|
coderay (~> 1.1)
|
||||||
|
method_source (~> 1.0)
|
||||||
|
reline (>= 0.6.0)
|
||||||
|
racc (1.8.1)
|
||||||
|
rake (13.4.2)
|
||||||
|
reline (0.6.3)
|
||||||
|
io-console (~> 0.5)
|
||||||
|
ruby-ole (1.2.13.1)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
x64-mingw-ucrt
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
concurrent-ruby (~> 1.1)
|
||||||
|
mathtype!
|
||||||
|
mathtype_to_mathml!
|
||||||
|
nokogiri (~> 1.13)
|
||||||
|
parallel
|
||||||
|
pry
|
||||||
|
rake (~> 13.0)
|
||||||
|
|
||||||
|
CHECKSUMS
|
||||||
|
bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58
|
||||||
|
bundler (4.0.15) sha256=a4ceb882fe94a0e0ac63cd0813932bbfd631a14e5ac0b7975189b19a4d28d9e7
|
||||||
|
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
|
||||||
|
concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
|
||||||
|
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
||||||
|
mathtype (0.1.0)
|
||||||
|
mathtype_to_mathml (0.0.7)
|
||||||
|
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
|
||||||
|
nokogiri (1.19.4-x64-mingw-ucrt) sha256=051da97b8eccfdb5444fed40246a35e10d7298b9efe759b4cd25455ea04c587e
|
||||||
|
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
||||||
|
pry (0.16.0) sha256=d76c69065698ed1f85e717bd33d7942c38a50868f6b0673c636192b3d1b6054e
|
||||||
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||||
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
||||||
|
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
||||||
|
ruby-ole (1.2.13.1) sha256=578d10dd2a797a2b35a1286c6fb2c9525f67c24791346fc8015d39f0ffa3cb72
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
4.0.15
|
||||||
182
scripts/mathtype2mathml/README.Docker.md
Normal file
182
scripts/mathtype2mathml/README.Docker.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
# MathType to MathML Docker 部署指南
|
||||||
|
|
||||||
|
这个 Docker 化版本让您可以轻松在任何支持 Docker 的平台上运行 MathType 到 MathML 转换服务。
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 方法一:使用 Docker Compose(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建并启动服务
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 停止服务
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法二:直接使用 Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建镜像
|
||||||
|
docker build -t mathtype-converter .
|
||||||
|
|
||||||
|
# 运行容器
|
||||||
|
docker run -d \
|
||||||
|
--name mathtype-to-mathml \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v $(pwd)/uploads:/app/uploads \
|
||||||
|
mathtype-converter
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker logs -f mathtype-to-mathml
|
||||||
|
|
||||||
|
# 停止容器
|
||||||
|
docker stop mathtype-to-mathml
|
||||||
|
docker rm mathtype-to-mathml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 使用服务
|
||||||
|
|
||||||
|
服务启动后,可以通过以下方式使用:
|
||||||
|
|
||||||
|
### Web 界面
|
||||||
|
打开浏览器访问:`http://localhost:3000/mathtype_converter_demo.html`
|
||||||
|
|
||||||
|
### API 接口
|
||||||
|
|
||||||
|
#### 健康检查
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 转换 MathType 文件
|
||||||
|
```bash
|
||||||
|
curl -X POST \
|
||||||
|
-F "file=@your_mathtype_file.bin" \
|
||||||
|
http://localhost:3000/convert-mathtype
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 配置说明
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
- `NODE_ENV`: 运行环境(默认:production)
|
||||||
|
- `PORT`: 服务端口(默认:3000)
|
||||||
|
|
||||||
|
### 数据卷
|
||||||
|
- `/app/uploads`: 上传文件存储目录
|
||||||
|
- `/app/config`: 配置文件目录(可选)
|
||||||
|
|
||||||
|
### 端口映射
|
||||||
|
- `3000`: Web 服务端口
|
||||||
|
|
||||||
|
## 📁 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
/app/
|
||||||
|
├── server_converter.js # Node.js 主服务器
|
||||||
|
├── server/
|
||||||
|
│ ├── converter.rb # Ruby 转换脚本
|
||||||
|
│ └── setup.js # 设置脚本
|
||||||
|
├── public/ # 静态文件
|
||||||
|
├── uploads/ # 上传文件目录
|
||||||
|
├── vendor/ # Ruby gems
|
||||||
|
└── Gemfile # Ruby 依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 故障排除
|
||||||
|
|
||||||
|
### 查看容器日志
|
||||||
|
```bash
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose logs mathtype-converter
|
||||||
|
|
||||||
|
# 直接 Docker
|
||||||
|
docker logs mathtype-to-mathml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 进入容器调试
|
||||||
|
```bash
|
||||||
|
# Docker Compose
|
||||||
|
docker-compose exec mathtype-converter bash
|
||||||
|
|
||||||
|
# 直接 Docker
|
||||||
|
docker exec -it mathtype-to-mathml bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### 重建镜像
|
||||||
|
```bash
|
||||||
|
# 清理并重建
|
||||||
|
docker-compose down
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 部署到生产环境
|
||||||
|
|
||||||
|
### 修改 docker-compose.yml
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
mathtype-converter:
|
||||||
|
# ... 其他配置
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
restart: always
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用反向代理(Nginx 示例)
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 文件上传大小限制
|
||||||
|
client_max_body_size 10M;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 监控和维护
|
||||||
|
|
||||||
|
### 健康检查
|
||||||
|
服务内置健康检查端点:`/health`
|
||||||
|
|
||||||
|
### 日志管理
|
||||||
|
```bash
|
||||||
|
# 设置日志轮转
|
||||||
|
docker-compose up -d --log-opt max-size=10m --log-opt max-file=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 备份上传文件
|
||||||
|
```bash
|
||||||
|
# 备份上传目录
|
||||||
|
docker run --rm -v mathtype_uploads:/data -v $(pwd):/backup alpine tar czf /backup/uploads-backup.tar.gz -C /data .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 更新服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 停止服务
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 拉取最新代码(如果有)
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# 重建并启动
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
115
scripts/mathtype2mathml/README.md
Normal file
115
scripts/mathtype2mathml/README.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# MathType to MathML Converter
|
||||||
|
|
||||||
|
一个基于 Ruby gem 的 MathType 二进制文件转 MathML 的 Node.js 服务器。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- 支持上传 MathType 二进制文件 (.bin)
|
||||||
|
- 转换为标准 MathML 格式
|
||||||
|
- RESTful API 接口
|
||||||
|
- Web 界面演示
|
||||||
|
- Docker 容器化部署
|
||||||
|
- 网络优化和重试机制
|
||||||
|
- 支持开发和生产环境配置
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 方式一:Docker 部署(推荐)
|
||||||
|
|
||||||
|
#### Windows 环境
|
||||||
|
```powershell
|
||||||
|
# 开发环境构建并启动
|
||||||
|
.\docker-build.ps1
|
||||||
|
|
||||||
|
# 生产环境构建并启动
|
||||||
|
.\docker-build.ps1 -Production
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
.\docker-build.ps1 -Help
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux/macOS 环境
|
||||||
|
```bash
|
||||||
|
# 增强构建脚本(推荐)
|
||||||
|
./docker-build-enhanced.sh
|
||||||
|
|
||||||
|
# 标准构建脚本
|
||||||
|
./docker-build.sh
|
||||||
|
|
||||||
|
# 或使用 docker-compose
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 生产环境
|
||||||
|
docker-compose -f docker-compose.production.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式二:本地部署
|
||||||
|
|
||||||
|
1. 确保安装了 Ruby 和 Node.js
|
||||||
|
2. 安装依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 Ruby 依赖
|
||||||
|
bundle install
|
||||||
|
|
||||||
|
# 安装 Node.js 依赖
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 启动服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 环境配置
|
||||||
|
|
||||||
|
- **开发环境**: 使用 `docker-compose.yml`,包含代码热重载
|
||||||
|
- **生产环境**: 使用 `docker-compose.production.yml`,优化性能和安全性
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
|
||||||
|
- `config/app.json`: 应用基础配置
|
||||||
|
- `uploads/`: 文件上传目录
|
||||||
|
- `vendor/gems/`: 本地 Ruby gems
|
||||||
|
|
||||||
|
## API 使用
|
||||||
|
|
||||||
|
### 转换 MathType 文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3000/convert-mathtype \
|
||||||
|
-F "file=@path/to/mathtype.bin"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 健康检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web 界面
|
||||||
|
|
||||||
|
启动服务后访问:http://localhost:3000/mathtype_converter_demo.html
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 网络问题
|
||||||
|
如果遇到 Docker Hub 连接问题:
|
||||||
|
1. 使用增强构建脚本:`./docker-build-enhanced.sh` 或 `.\docker-build.ps1`
|
||||||
|
2. 脚本会自动检测网络状况并使用国内镜像源
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
- **端口冲突**: 修改 docker-compose.yml 中的端口映射
|
||||||
|
- **构建失败**: 清理 Docker 缓存 `docker system prune`
|
||||||
|
- **权限问题**: 确保 uploads 目录权限正确
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **后端**: Node.js + Express
|
||||||
|
- **Ruby Gem**: mathtype_to_mathml
|
||||||
|
- **容器化**: Docker + Docker Compose
|
||||||
|
- **前端**: 原生 HTML/JavaScript
|
||||||
|
- **网络优化**: 国内镜像源支持
|
||||||
217
scripts/mathtype2mathml/SETUP_GUIDE.md
Normal file
217
scripts/mathtype2mathml/SETUP_GUIDE.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# 🔧 Setup Guide: Converting Real MathType .bin Files
|
||||||
|
|
||||||
|
## Why You Need This Setup
|
||||||
|
|
||||||
|
The JavaScript client-side converter only shows a sample equation (x²) because **parsing MathType's proprietary binary format requires the Ruby gem**. To convert real `.bin` files, you need the server setup.
|
||||||
|
|
||||||
|
## 📋 Prerequisites
|
||||||
|
|
||||||
|
### 1. Install Ruby and Gems
|
||||||
|
```bash
|
||||||
|
# On Windows (using RubyInstaller)
|
||||||
|
# Download from: https://rubyinstaller.org/
|
||||||
|
|
||||||
|
# On macOS (using Homebrew)
|
||||||
|
brew install ruby
|
||||||
|
|
||||||
|
# On Ubuntu/Debian
|
||||||
|
sudo apt-get install ruby-full
|
||||||
|
|
||||||
|
# Install bundler and the required gems from the project root
|
||||||
|
gem install bundler
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install Node.js
|
||||||
|
```bash
|
||||||
|
# Download from: https://nodejs.org/
|
||||||
|
# Or using package managers:
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
brew install node
|
||||||
|
|
||||||
|
# Ubuntu/Debian
|
||||||
|
sudo apt-get install nodejs npm
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
# Download installer from nodejs.org
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Quick Setup
|
||||||
|
|
||||||
|
### Step 1: Install Server Dependencies
|
||||||
|
```bash
|
||||||
|
# In your project's server directory
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Start the Server
|
||||||
|
```bash
|
||||||
|
# From the project root
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
```
|
||||||
|
MathType converter server running at http://localhost:3000
|
||||||
|
Make sure you have the mathtype_to_mathml Ruby gem installed.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Open the Demo
|
||||||
|
1. Open `http://localhost:3000/index.html`
|
||||||
|
2. Upload a real MathType `.bin` file
|
||||||
|
3. Click "Convert Real File (Server Required)"
|
||||||
|
|
||||||
|
## 📁 Finding MathType .bin Files
|
||||||
|
|
||||||
|
MathType equations in Word documents are typically stored as:
|
||||||
|
- `oleObject1.bin`, `oleObject2.bin`, etc.
|
||||||
|
- In the `word/embeddings/` folder when you unzip a `.docx` file
|
||||||
|
|
||||||
|
### Extract from Word Document:
|
||||||
|
```bash
|
||||||
|
# Rename .docx to .zip and extract
|
||||||
|
cp document.docx document.zip
|
||||||
|
unzip document.zip
|
||||||
|
ls word/embeddings/ # Look for .bin files
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 Testing the Setup
|
||||||
|
|
||||||
|
### Test 1: Server Health Check
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
# Should return: {"status":"ok","message":"MathType converter server is running"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test 2: Convert a File via API
|
||||||
|
```bash
|
||||||
|
# Make sure to be in the directory with oleObject1.bin
|
||||||
|
curl -X POST -F "file=@oleObject1.bin" http://localhost:3000/convert-mathtype
|
||||||
|
# Should return JSON with MathML
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test 3: Web Interface
|
||||||
|
1. Open browser to `http://localhost:3000/index.html`
|
||||||
|
2. Upload a `.bin` file
|
||||||
|
3. Click "Convert Real File"
|
||||||
|
4. Should see actual MathML output (not just x²)
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Error: "Ruby conversion failed"
|
||||||
|
```bash
|
||||||
|
# Check if Ruby gems are installed via bundle from the project root
|
||||||
|
bundle check
|
||||||
|
|
||||||
|
# Reinstall if missing
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "Failed to spawn Ruby process"
|
||||||
|
```bash
|
||||||
|
# Check if Ruby is in PATH
|
||||||
|
ruby --version
|
||||||
|
|
||||||
|
# The script now runs from the project root, so bundle should find the correct ruby executable.
|
||||||
|
# If you still have issues, ensure 'bundle' command is in your system's PATH.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "Cannot find module 'express'"
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install express multer cors
|
||||||
|
cd ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error: "File upload failed"
|
||||||
|
- Check file size (limit is 10MB)
|
||||||
|
- Ensure file has `.bin` extension
|
||||||
|
- Verify file is actually a MathType binary
|
||||||
|
|
||||||
|
## 📊 Expected Behavior
|
||||||
|
|
||||||
|
### ✅ Success Case:
|
||||||
|
1. Upload `oleObject1.bin` containing equation `E = mc²`
|
||||||
|
2. Server processes file with Ruby gem
|
||||||
|
3. Returns: `<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></math>`
|
||||||
|
4. Browser displays rendered equation
|
||||||
|
|
||||||
|
### ❌ Fallback Case:
|
||||||
|
1. Server unavailable or fails
|
||||||
|
2. Client falls back to demo mode
|
||||||
|
3. Always shows `x²` sample regardless of uploaded file
|
||||||
|
4. Warning message: "Client-side demo completed (shows sample x² only)"
|
||||||
|
|
||||||
|
## 🔄 Development Workflow
|
||||||
|
|
||||||
|
### For Development:
|
||||||
|
```bash
|
||||||
|
# Use nodemon for auto-restart (run from project root)
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### For Production:
|
||||||
|
```bash
|
||||||
|
# Use PM2 or similar process manager (run from project root)
|
||||||
|
npm install -g pm2
|
||||||
|
pm2 start server/server.js --name mathtype-converter
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📈 Performance Notes
|
||||||
|
|
||||||
|
- **Ruby Process**: Each conversion spawns a Ruby process (~100-500ms overhead)
|
||||||
|
- **File Size**: Typical `.bin` files are 1-50KB
|
||||||
|
- **Concurrent Requests**: Server handles multiple files simultaneously
|
||||||
|
- **Memory**: Each Ruby process uses ~10-50MB temporarily
|
||||||
|
|
||||||
|
## 🔒 Security Considerations
|
||||||
|
|
||||||
|
For production deployment:
|
||||||
|
- Add file type validation
|
||||||
|
- Implement rate limiting
|
||||||
|
- Add authentication if needed
|
||||||
|
- Sanitize file names
|
||||||
|
- Use temporary directories with cleanup
|
||||||
|
|
||||||
|
## 📦 Alternative Deployment
|
||||||
|
|
||||||
|
### Docker Option:
|
||||||
|
```dockerfile
|
||||||
|
FROM node:18
|
||||||
|
RUN apt-get update && apt-get install -y ruby-full
|
||||||
|
RUN gem install bundler
|
||||||
|
|
||||||
|
# Set work directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy gem files and install gems
|
||||||
|
COPY Gemfile Gemfile.lock ./
|
||||||
|
COPY vendor ./vendor
|
||||||
|
RUN bundle install
|
||||||
|
|
||||||
|
# Copy server files and install node modules
|
||||||
|
COPY server ./server
|
||||||
|
WORKDIR /app/server
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Go back to root to run the server
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["npm", "start"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloud Function Option:
|
||||||
|
Deploy to AWS Lambda, Google Cloud Functions, or Vercel with Ruby runtime support.
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
1. Check Ruby gem installation from root: `bundle check`
|
||||||
|
2. Verify Node.js setup in server folder: `cd server && npm install && cd ..`
|
||||||
|
3. Test with sample `.bin` files first
|
||||||
|
4. Check server logs for detailed error messages
|
||||||
|
|
||||||
|
The key is having both Ruby (for binary parsing) and Node.js (for web interface) working together!
|
||||||
211
scripts/mathtype2mathml/batch_converter.rb
Normal file
211
scripts/mathtype2mathml/batch_converter.rb
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
require_relative 'vendor/gems/mathtype_to_mathml/lib/mathtype_to_mathml'
|
||||||
|
require 'json'
|
||||||
|
require 'concurrent-ruby'
|
||||||
|
require 'pathname'
|
||||||
|
require 'parallel'
|
||||||
|
|
||||||
|
class BatchConverter
|
||||||
|
def initialize(max_threads: 4)
|
||||||
|
@max_threads = max_threads
|
||||||
|
@thread_pool = Concurrent::ThreadPoolExecutor.new(
|
||||||
|
min_threads: 1,
|
||||||
|
max_threads: @max_threads,
|
||||||
|
max_queue: 100,
|
||||||
|
fallback_policy: :caller_runs
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# 新增多进程处理方法
|
||||||
|
def process_files_multi_process(file_paths)
|
||||||
|
# 确保传入的是可枚举的哈希结构
|
||||||
|
file_paths_hash = file_paths.is_a?(Hash) ? file_paths : {}
|
||||||
|
# 根据平台自动选择并行模式:
|
||||||
|
# Linux/macOS 支持 Process.fork,使用多进程实现真正的 CPU 并行
|
||||||
|
# Windows 不支持 Process.fork,降级为多线程
|
||||||
|
parallel_opts = Process.respond_to?(:fork) ? { in_processes: @max_threads } : { in_threads: @max_threads }
|
||||||
|
result_array = Parallel.map(file_paths_hash.to_a, parallel_opts) do |key, value|
|
||||||
|
begin
|
||||||
|
result = process_single_file(value)
|
||||||
|
[key, result]
|
||||||
|
rescue => e
|
||||||
|
[key, {
|
||||||
|
success: false,
|
||||||
|
error: e.message,
|
||||||
|
mathml: nil
|
||||||
|
}]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# 将数组结果转换为哈希
|
||||||
|
result_array.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_files(file_paths)
|
||||||
|
results = {}
|
||||||
|
futures = []
|
||||||
|
|
||||||
|
# 遍历json入参开始转换
|
||||||
|
file_paths.each do |key, value|
|
||||||
|
future = Concurrent::Future.execute(executor: @thread_pool) do
|
||||||
|
process_single_file(value)
|
||||||
|
end
|
||||||
|
futures << [key, future]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Wait for all conversions to complete
|
||||||
|
futures.each do |file_path, future|
|
||||||
|
begin
|
||||||
|
result = future.value(30) # 30 second timeout per file
|
||||||
|
results[file_path] = result
|
||||||
|
rescue Concurrent::TimeoutError
|
||||||
|
results[file_path] = {
|
||||||
|
success: false,
|
||||||
|
error: 'Conversion timeout (30 seconds)',
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
rescue => e
|
||||||
|
results[file_path] = {
|
||||||
|
success: false,
|
||||||
|
error: e.message,
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@thread_pool.shutdown
|
||||||
|
@thread_pool.wait_for_termination(60)
|
||||||
|
|
||||||
|
results
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_single_file(file_path)
|
||||||
|
begin
|
||||||
|
# Validate file
|
||||||
|
unless File.exist?(file_path)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "File does not exist: #{file_path}",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
unless File.readable?(file_path)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "File is not readable: #{file_path}",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
file_size = File.size(file_path)
|
||||||
|
if file_size == 0
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "File is empty: #{file_path}",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convert the file
|
||||||
|
converter = MathTypeToMathML::Converter.new(file_path)
|
||||||
|
mathml = converter.convert
|
||||||
|
|
||||||
|
# Validate output
|
||||||
|
if mathml.nil? || mathml.strip.empty?
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: "Conversion resulted in empty MathML",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
error: nil,
|
||||||
|
mathml: mathml.strip,
|
||||||
|
file_size: file_size
|
||||||
|
}
|
||||||
|
|
||||||
|
rescue NoMethodError => e
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: "Ruby parsing error - #{e.message}. This usually indicates an incompatible or corrupted MathType file",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
rescue ArgumentError => e
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: "Invalid file format - #{e.message}",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
rescue StandardError => e
|
||||||
|
{
|
||||||
|
success: false,
|
||||||
|
error: "Unexpected error - #{e.class}: #{e.message}",
|
||||||
|
mathml: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Command line interface
|
||||||
|
if __FILE__ == $0
|
||||||
|
begin
|
||||||
|
if ARGV.empty?
|
||||||
|
STDERR.puts "Usage: #{$0} file1.bin [file2.bin ...]"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Validate all files exist before processing
|
||||||
|
# missing_files = ARGV.reject { |file| File.exist?(file) }
|
||||||
|
# unless missing_files.empty?
|
||||||
|
# STDERR.puts "Error: Files not found: #{missing_files.join(', ')}"
|
||||||
|
# exit 1
|
||||||
|
# end
|
||||||
|
|
||||||
|
# 入参可以是文件路径或JSON字符串
|
||||||
|
arg = ARGV[0] || '{}'
|
||||||
|
if File.exist?(arg) && File.file?(arg)
|
||||||
|
json_data = File.read(arg, mode: 'rb:utf-8').gsub(/\A\xEF\xBB\xBF/, '')
|
||||||
|
else
|
||||||
|
json_data = arg.gsub(/\A'|'\z/, '')
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
data = JSON.parse(json_data)
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
STDERR.puts "ERROR: Invalid JSON input: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Process files
|
||||||
|
# batch_converter = BatchConverter.new(max_threads: [ARGV.size, 8].min)
|
||||||
|
# results = batch_converter.process_files(data)
|
||||||
|
|
||||||
|
batch_converter = BatchConverter.new(max_threads: [data.size, Parallel.processor_count].min)
|
||||||
|
results = batch_converter.process_files_multi_process(data)
|
||||||
|
|
||||||
|
# Output results as JSON
|
||||||
|
output = {
|
||||||
|
total_files: data.size,
|
||||||
|
successful: results.count { |_, result| result[:success] },
|
||||||
|
failed: results.count { |_, result| !result[:success] },
|
||||||
|
results: results
|
||||||
|
}
|
||||||
|
|
||||||
|
puts "WP_RUBY_".concat(output.to_json)
|
||||||
|
# puts JSON.pretty_generate(output)
|
||||||
|
|
||||||
|
# Exit with error code if any conversions failed
|
||||||
|
exit_code = results.any? { |_, result| !result[:success] } ? 1 : 0
|
||||||
|
# exit exit_code
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
rescue Interrupt
|
||||||
|
STDERR.puts "\nBatch conversion interrupted by user"
|
||||||
|
exit 130
|
||||||
|
rescue => e
|
||||||
|
STDERR.puts "Fatal error: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
1
scripts/mathtype2mathml/config/.gitkeep
Normal file
1
scripts/mathtype2mathml/config/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Keep this directory in git
|
||||||
19
scripts/mathtype2mathml/config/app.json
Normal file
19
scripts/mathtype2mathml/config/app.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "MathType to MathML Converter",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Server for converting MathType binary files to MathML",
|
||||||
|
"port": 3000,
|
||||||
|
"environment": "production",
|
||||||
|
"limits": {
|
||||||
|
"maxFileSize": "10MB",
|
||||||
|
"maxFiles": 10
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "info",
|
||||||
|
"format": "combined"
|
||||||
|
},
|
||||||
|
"cors": {
|
||||||
|
"enabled": true,
|
||||||
|
"origin": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
65
scripts/mathtype2mathml/converter.rb
Normal file
65
scripts/mathtype2mathml/converter.rb
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
require 'mathtype_to_mathml'
|
||||||
|
|
||||||
|
def safe_convert(file_path)
|
||||||
|
begin
|
||||||
|
# Check if file exists and is readable
|
||||||
|
unless File.exist?(file_path)
|
||||||
|
STDERR.puts "Error: File does not exist: #{file_path}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
unless File.readable?(file_path)
|
||||||
|
STDERR.puts "Error: File is not readable: #{file_path}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# Check file size
|
||||||
|
file_size = File.size(file_path)
|
||||||
|
if file_size == 0
|
||||||
|
STDERR.puts "Error: File is empty: #{file_path}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
STDERR.puts "Processing file: #{file_path} (#{file_size} bytes)"
|
||||||
|
|
||||||
|
# Try to convert the file
|
||||||
|
converter = MathTypeToMathML::Converter.new(file_path)
|
||||||
|
mathml = converter.convert
|
||||||
|
|
||||||
|
# Check if we got valid output
|
||||||
|
if mathml.nil? || mathml.strip.empty?
|
||||||
|
STDERR.puts "Error: Conversion resulted in empty MathML"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
puts mathml
|
||||||
|
|
||||||
|
rescue NoMethodError => e
|
||||||
|
STDERR.puts "Error: Ruby parsing error - #{e.message}"
|
||||||
|
STDERR.puts "This usually indicates an incompatible or corrupted MathType file"
|
||||||
|
STDERR.puts "Backtrace: #{e.backtrace.first(3).join(', ')}"
|
||||||
|
exit 1
|
||||||
|
rescue ArgumentError => e
|
||||||
|
STDERR.puts "Error: Invalid file format - #{e.message}"
|
||||||
|
exit 1
|
||||||
|
rescue StandardError => e
|
||||||
|
STDERR.puts "Error: Unexpected error - #{e.class}: #{e.message}"
|
||||||
|
STDERR.puts "Backtrace: #{e.backtrace.first(5).join(', ')}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
file_path = ARGV[0]
|
||||||
|
|
||||||
|
if file_path.nil? || file_path.empty?
|
||||||
|
STDERR.puts "Error: No file path provided"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
safe_convert(file_path)
|
||||||
|
|
||||||
|
rescue => e
|
||||||
|
STDERR.puts "Error: #{e.message}"
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
182
scripts/mathtype2mathml/docker-build-enhanced.sh
Normal file
182
scripts/mathtype2mathml/docker-build-enhanced.sh
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Enhanced Docker 构建脚本 - 网络优化版本
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🐳 Enhanced MathType to MathML Docker 构建脚本"
|
||||||
|
echo "================================================="
|
||||||
|
|
||||||
|
# 检查 Docker 是否安装
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "❌ Docker 未安装,请先安装 Docker"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 docker-compose 是否安装
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
echo "⚠️ docker-compose 未安装,将使用 docker build"
|
||||||
|
USE_COMPOSE=false
|
||||||
|
else
|
||||||
|
USE_COMPOSE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置镜像名称
|
||||||
|
IMAGE_NAME="mathtype-converter"
|
||||||
|
CONTAINER_NAME="mathtype-to-mathml"
|
||||||
|
|
||||||
|
# 检查网络连接并选择最佳构建策略
|
||||||
|
echo "🌐 检查网络连接..."
|
||||||
|
|
||||||
|
# 测试Docker Hub连接
|
||||||
|
if curl -s --connect-timeout 5 https://registry.hub.docker.com/ > /dev/null; then
|
||||||
|
echo "✅ Docker Hub 连接正常"
|
||||||
|
NETWORK_MODE="standard"
|
||||||
|
else
|
||||||
|
echo "⚠️ Docker Hub 连接异常,使用网络优化模式"
|
||||||
|
NETWORK_MODE="optimized"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 根据网络状况选择构建参数
|
||||||
|
if [ "$NETWORK_MODE" = "optimized" ]; then
|
||||||
|
echo "📦 使用网络优化构建..."
|
||||||
|
BUILD_ARGS="--build-arg USE_MIRROR=true --network=host"
|
||||||
|
else
|
||||||
|
echo "📦 使用标准构建..."
|
||||||
|
BUILD_ARGS=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔨 开始构建 Docker 镜像..."
|
||||||
|
|
||||||
|
# 构建函数
|
||||||
|
build_image() {
|
||||||
|
local attempt=$1
|
||||||
|
echo "📝 构建尝试 $attempt/3..."
|
||||||
|
|
||||||
|
if [ "$USE_COMPOSE" = true ]; then
|
||||||
|
if docker-compose build $BUILD_ARGS; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if docker build -t $IMAGE_NAME $BUILD_ARGS .; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 重试构建
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if build_image $attempt; then
|
||||||
|
echo "✅ Docker 镜像构建成功!"
|
||||||
|
BUILD_SUCCESS=true
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "❌ 构建尝试 $attempt 失败"
|
||||||
|
if [ $attempt -lt 3 ]; then
|
||||||
|
echo "⏳ 等待 10 秒后重试..."
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
BUILD_SUCCESS=false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$BUILD_SUCCESS" != true ]; then
|
||||||
|
echo "❌ 所有构建尝试都失败了"
|
||||||
|
echo "💡 建议:"
|
||||||
|
echo " 1. 检查网络连接"
|
||||||
|
echo " 2. 确认 Docker 服务运行正常"
|
||||||
|
echo " 3. 尝试手动清理 Docker 缓存: docker system prune"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证镜像构建
|
||||||
|
echo "🔍 验证镜像..."
|
||||||
|
if docker images | grep -q $IMAGE_NAME; then
|
||||||
|
echo "✅ 镜像验证成功!"
|
||||||
|
else
|
||||||
|
echo "❌ 镜像验证失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 提供启动选项
|
||||||
|
echo ""
|
||||||
|
echo "🚀 启动选项:"
|
||||||
|
echo "1. 使用 docker-compose(推荐):"
|
||||||
|
echo " docker-compose up -d"
|
||||||
|
echo ""
|
||||||
|
echo "2. 直接使用 Docker:"
|
||||||
|
echo " docker run -d --name $CONTAINER_NAME -p 3000:3000 -v \$(pwd)/uploads:/app/uploads $IMAGE_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "📱 启动后访问:"
|
||||||
|
echo " Web界面: http://localhost:3000/mathtype_converter_demo.html"
|
||||||
|
echo " 健康检查: http://localhost:3000/health"
|
||||||
|
echo ""
|
||||||
|
echo "📋 管理命令:"
|
||||||
|
echo " 查看日志: docker logs -f $CONTAINER_NAME"
|
||||||
|
echo " 停止容器: docker stop $CONTAINER_NAME"
|
||||||
|
echo " 删除容器: docker rm $CONTAINER_NAME"
|
||||||
|
|
||||||
|
# 询问是否立即启动
|
||||||
|
read -p "是否立即启动容器? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "🚀 启动容器..."
|
||||||
|
if [ "$USE_COMPOSE" = true ]; then
|
||||||
|
if docker-compose up -d; then
|
||||||
|
echo "✅ 容器已启动!"
|
||||||
|
echo "📊 查看状态: docker-compose ps"
|
||||||
|
echo "📋 查看日志: docker-compose logs -f"
|
||||||
|
else
|
||||||
|
echo "❌ 容器启动失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 停止并删除已存在的容器
|
||||||
|
docker stop $CONTAINER_NAME 2>/dev/null || true
|
||||||
|
docker rm $CONTAINER_NAME 2>/dev/null || true
|
||||||
|
|
||||||
|
# 启动新容器
|
||||||
|
if docker run -d \
|
||||||
|
--name $CONTAINER_NAME \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v "$(pwd)/uploads:/app/uploads" \
|
||||||
|
-v "$(pwd)/config:/app/config:ro" \
|
||||||
|
$IMAGE_NAME; then
|
||||||
|
|
||||||
|
echo "✅ 容器已启动!"
|
||||||
|
echo "📊 查看状态: docker ps"
|
||||||
|
echo "📋 查看日志: docker logs -f $CONTAINER_NAME"
|
||||||
|
else
|
||||||
|
echo "❌ 容器启动失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "⏳ 等待服务启动..."
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
echo "🏥 执行健康检查..."
|
||||||
|
for i in {1..6}; do
|
||||||
|
if curl -s http://localhost:3000/health > /dev/null; then
|
||||||
|
echo "✅ 服务启动成功!"
|
||||||
|
echo "🌐 请访问: http://localhost:3000/mathtype_converter_demo.html"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
if [ $i -eq 6 ]; then
|
||||||
|
echo "⚠️ 健康检查超时,但服务可能仍在启动中"
|
||||||
|
echo "💡 请手动检查: curl http://localhost:3000/health"
|
||||||
|
else
|
||||||
|
echo "⏳ 等待服务启动... ($i/6)"
|
||||||
|
sleep 5
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 构建完成!"
|
||||||
216
scripts/mathtype2mathml/docker-build.ps1
Normal file
216
scripts/mathtype2mathml/docker-build.ps1
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# Enhanced Docker 构建脚本 - Windows PowerShell 版本
|
||||||
|
param(
|
||||||
|
[switch]$Production,
|
||||||
|
[switch]$NoBuild,
|
||||||
|
[switch]$Help
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($Help) {
|
||||||
|
Write-Host "🐳 MathType to MathML Docker 构建脚本 - Windows版本" -ForegroundColor Cyan
|
||||||
|
Write-Host "用法: .\docker-build.ps1 [参数]" -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "参数:" -ForegroundColor Yellow
|
||||||
|
Write-Host " -Production 使用生产环境配置 (docker-compose.production.yml)"
|
||||||
|
Write-Host " -NoBuild 跳过构建,直接启动已有镜像"
|
||||||
|
Write-Host " -Help 显示此帮助信息"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "示例:" -ForegroundColor Yellow
|
||||||
|
Write-Host " .\docker-build.ps1 # 开发环境构建和启动"
|
||||||
|
Write-Host " .\docker-build.ps1 -Production # 生产环境构建和启动"
|
||||||
|
Write-Host " .\docker-build.ps1 -NoBuild # 跳过构建,直接启动"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
Write-Host "🐳 Enhanced MathType to MathML Docker 构建脚本" -ForegroundColor Cyan
|
||||||
|
Write-Host "=================================================" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 检查 Docker 是否安装
|
||||||
|
if (!(Get-Command docker -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "❌ Docker 未安装,请先安装 Docker Desktop" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查 docker-compose 是否安装
|
||||||
|
if (!(Get-Command docker-compose -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "⚠️ docker-compose 未安装,将使用 docker build" -ForegroundColor Yellow
|
||||||
|
$UseCompose = $false
|
||||||
|
} else {
|
||||||
|
$UseCompose = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# 设置镜像名称和配置文件
|
||||||
|
$ImageName = "mathtype-converter"
|
||||||
|
$ContainerName = "mathtype-to-mathml"
|
||||||
|
|
||||||
|
if ($Production) {
|
||||||
|
$ComposeFile = "docker-compose.production.yml"
|
||||||
|
$ContainerName += "-prod"
|
||||||
|
Write-Host "📦 使用生产环境配置" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
$ComposeFile = "docker-compose.yml"
|
||||||
|
Write-Host "📦 使用开发环境配置" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查网络连接
|
||||||
|
Write-Host "🌐 检查网络连接..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest -Uri "https://registry.hub.docker.com/" -TimeoutSec 5 -UseBasicParsing
|
||||||
|
Write-Host "✅ Docker Hub 连接正常" -ForegroundColor Green
|
||||||
|
$NetworkMode = "standard"
|
||||||
|
} catch {
|
||||||
|
Write-Host "⚠️ Docker Hub 连接异常,使用网络优化模式" -ForegroundColor Yellow
|
||||||
|
$NetworkMode = "optimized"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$NoBuild) {
|
||||||
|
Write-Host "🔨 开始构建 Docker 镜像..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# 构建函数
|
||||||
|
function Build-DockerImage {
|
||||||
|
param([int]$Attempt)
|
||||||
|
|
||||||
|
Write-Host "📝 构建尝试 $Attempt/3..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($UseCompose) {
|
||||||
|
if ($NetworkMode -eq "optimized") {
|
||||||
|
docker-compose -f $ComposeFile build --build-arg USE_MIRROR=true
|
||||||
|
} else {
|
||||||
|
docker-compose -f $ComposeFile build
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($NetworkMode -eq "optimized") {
|
||||||
|
docker build -t $ImageName --build-arg USE_MIRROR=true .
|
||||||
|
} else {
|
||||||
|
docker build -t $ImageName .
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
Write-Host "❌ 构建失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 重试构建
|
||||||
|
$BuildSuccess = $false
|
||||||
|
for ($attempt = 1; $attempt -le 3; $attempt++) {
|
||||||
|
if (Build-DockerImage -Attempt $attempt) {
|
||||||
|
Write-Host "✅ Docker 镜像构建成功!" -ForegroundColor Green
|
||||||
|
$BuildSuccess = $true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
if ($attempt -lt 3) {
|
||||||
|
Write-Host "⏳ 等待 10 秒后重试..." -ForegroundColor Yellow
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$BuildSuccess) {
|
||||||
|
Write-Host "❌ 所有构建尝试都失败了" -ForegroundColor Red
|
||||||
|
Write-Host "💡 建议:" -ForegroundColor Yellow
|
||||||
|
Write-Host " 1. 检查网络连接"
|
||||||
|
Write-Host " 2. 确认 Docker Desktop 运行正常"
|
||||||
|
Write-Host " 3. 尝试手动清理 Docker 缓存: docker system prune"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 验证镜像构建
|
||||||
|
Write-Host "🔍 验证镜像..." -ForegroundColor Yellow
|
||||||
|
$images = docker images --format "{{.Repository}}" | Where-Object { $_ -eq $ImageName }
|
||||||
|
if ($images) {
|
||||||
|
Write-Host "✅ 镜像验证成功!" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "❌ 镜像验证失败" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "⏭️ 跳过构建步骤" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
# 提供启动选项
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "🚀 启动选项:" -ForegroundColor Cyan
|
||||||
|
if ($UseCompose) {
|
||||||
|
Write-Host "1. 使用 docker-compose(推荐):" -ForegroundColor White
|
||||||
|
Write-Host " docker-compose -f $ComposeFile up -d" -ForegroundColor Gray
|
||||||
|
} else {
|
||||||
|
Write-Host "1. 直接使用 Docker:" -ForegroundColor White
|
||||||
|
Write-Host " docker run -d --name $ContainerName -p 3000:3000 -v `$(pwd)/uploads:/app/uploads -v `$(pwd)/config:/app/config:ro $ImageName" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "📱 启动后访问:" -ForegroundColor Cyan
|
||||||
|
Write-Host " Web界面: http://localhost:3000/mathtype_converter_demo.html" -ForegroundColor White
|
||||||
|
Write-Host " 健康检查: http://localhost:3000/health" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "📋 管理命令:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 查看日志: docker logs -f $ContainerName" -ForegroundColor Gray
|
||||||
|
Write-Host " 停止容器: docker stop $ContainerName" -ForegroundColor Gray
|
||||||
|
Write-Host " 删除容器: docker rm $ContainerName" -ForegroundColor Gray
|
||||||
|
|
||||||
|
# 询问是否立即启动
|
||||||
|
$reply = Read-Host "`n是否立即启动容器? (y/N)"
|
||||||
|
if ($reply -match "^[Yy]$") {
|
||||||
|
Write-Host "🚀 启动容器..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($UseCompose) {
|
||||||
|
docker-compose -f $ComposeFile up -d
|
||||||
|
Write-Host "✅ 容器已启动!" -ForegroundColor Green
|
||||||
|
Write-Host "📊 查看状态: docker-compose -f $ComposeFile ps" -ForegroundColor Gray
|
||||||
|
Write-Host "📋 查看日志: docker-compose -f $ComposeFile logs -f" -ForegroundColor Gray
|
||||||
|
} else {
|
||||||
|
# 停止并删除已存在的容器
|
||||||
|
try { docker stop $ContainerName 2>$null } catch {}
|
||||||
|
try { docker rm $ContainerName 2>$null } catch {}
|
||||||
|
|
||||||
|
# 启动新容器
|
||||||
|
$currentDir = Get-Location
|
||||||
|
docker run -d `
|
||||||
|
--name $ContainerName `
|
||||||
|
-p 3000:3000 `
|
||||||
|
-v "${currentDir}/uploads:/app/uploads" `
|
||||||
|
-v "${currentDir}/config:/app/config:ro" `
|
||||||
|
$ImageName
|
||||||
|
|
||||||
|
Write-Host "✅ 容器已启动!" -ForegroundColor Green
|
||||||
|
Write-Host "📊 查看状态: docker ps" -ForegroundColor Gray
|
||||||
|
Write-Host "📋 查看日志: docker logs -f $ContainerName" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "⏳ 等待服务启动..." -ForegroundColor Yellow
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
Write-Host "🏥 执行健康检查..." -ForegroundColor Yellow
|
||||||
|
$healthCheckSuccess = $false
|
||||||
|
for ($i = 1; $i -le 6; $i++) {
|
||||||
|
try {
|
||||||
|
$response = Invoke-WebRequest -Uri "http://localhost:3000/health" -TimeoutSec 5 -UseBasicParsing
|
||||||
|
Write-Host "✅ 服务启动成功!" -ForegroundColor Green
|
||||||
|
Write-Host "🌐 请访问: http://localhost:3000/mathtype_converter_demo.html" -ForegroundColor Cyan
|
||||||
|
$healthCheckSuccess = $true
|
||||||
|
break
|
||||||
|
} catch {
|
||||||
|
if ($i -eq 6) {
|
||||||
|
Write-Host "⚠️ 健康检查超时,但服务可能仍在启动中" -ForegroundColor Yellow
|
||||||
|
Write-Host "💡 请手动检查: Invoke-WebRequest http://localhost:3000/health" -ForegroundColor Gray
|
||||||
|
} else {
|
||||||
|
Write-Host "⏳ 等待服务启动... ($i/6)" -ForegroundColor Yellow
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host "❌ 容器启动失败: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "🎉 构建完成!" -ForegroundColor Green
|
||||||
96
scripts/mathtype2mathml/docker-build.sh
Normal file
96
scripts/mathtype2mathml/docker-build.sh
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Docker 构建和测试脚本
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🐳 MathType to MathML Docker 构建脚本"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# 检查 Docker 是否安装
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "❌ Docker 未安装,请先安装 Docker"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 docker-compose 是否安装
|
||||||
|
if ! command -v docker-compose &> /dev/null; then
|
||||||
|
echo "⚠️ docker-compose 未安装,将使用 docker build"
|
||||||
|
USE_COMPOSE=false
|
||||||
|
else
|
||||||
|
USE_COMPOSE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 设置镜像名称
|
||||||
|
IMAGE_NAME="mathtype-converter"
|
||||||
|
CONTAINER_NAME="mathtype-to-mathml"
|
||||||
|
|
||||||
|
echo "📦 开始构建 Docker 镜像..."
|
||||||
|
|
||||||
|
if [ "$USE_COMPOSE" = true ]; then
|
||||||
|
echo "使用 docker-compose 构建..."
|
||||||
|
docker-compose build
|
||||||
|
else
|
||||||
|
echo "使用 docker build 构建..."
|
||||||
|
docker build -t $IMAGE_NAME .
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Docker 镜像构建完成!"
|
||||||
|
|
||||||
|
# 提供启动选项
|
||||||
|
echo ""
|
||||||
|
echo "🚀 启动选项:"
|
||||||
|
echo "1. 使用 docker-compose(推荐):"
|
||||||
|
echo " docker-compose up -d"
|
||||||
|
echo ""
|
||||||
|
echo "2. 直接使用 Docker:"
|
||||||
|
echo " docker run -d --name $CONTAINER_NAME -p 3000:3000 -v \$(pwd)/uploads:/app/uploads $IMAGE_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "📱 启动后访问:"
|
||||||
|
echo " Web界面: http://localhost:3000/mathtype_converter_demo.html"
|
||||||
|
echo " 健康检查: http://localhost:3000/health"
|
||||||
|
echo ""
|
||||||
|
echo "📋 管理命令:"
|
||||||
|
echo " 查看日志: docker logs -f $CONTAINER_NAME"
|
||||||
|
echo " 停止容器: docker stop $CONTAINER_NAME"
|
||||||
|
echo " 删除容器: docker rm $CONTAINER_NAME"
|
||||||
|
|
||||||
|
# 询问是否立即启动
|
||||||
|
read -p "是否立即启动容器? (y/N): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "🚀 启动容器..."
|
||||||
|
if [ "$USE_COMPOSE" = true ]; then
|
||||||
|
docker-compose up -d
|
||||||
|
echo "✅ 容器已启动!"
|
||||||
|
echo "📊 查看状态: docker-compose ps"
|
||||||
|
echo "📋 查看日志: docker-compose logs -f"
|
||||||
|
else
|
||||||
|
# 停止并删除已存在的容器
|
||||||
|
docker stop $CONTAINER_NAME 2>/dev/null || true
|
||||||
|
docker rm $CONTAINER_NAME 2>/dev/null || true
|
||||||
|
|
||||||
|
# 启动新容器
|
||||||
|
docker run -d \
|
||||||
|
--name $CONTAINER_NAME \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v "$(pwd)/uploads:/app/uploads" \
|
||||||
|
$IMAGE_NAME
|
||||||
|
|
||||||
|
echo "✅ 容器已启动!"
|
||||||
|
echo "📊 查看状态: docker ps"
|
||||||
|
echo "📋 查看日志: docker logs -f $CONTAINER_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "⏳ 等待服务启动..."
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 健康检查
|
||||||
|
echo "🏥 执行健康检查..."
|
||||||
|
if curl -s http://localhost:3000/health > /dev/null; then
|
||||||
|
echo "✅ 服务启动成功!"
|
||||||
|
echo "🌐 请访问: http://localhost:3000/mathtype_converter_demo.html"
|
||||||
|
else
|
||||||
|
echo "⚠️ 服务可能还在启动中,请稍等片刻后再次访问"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
29
scripts/mathtype2mathml/docker-compose.production.yml
Normal file
29
scripts/mathtype2mathml/docker-compose.production.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
services:
|
||||||
|
mathtype-converter:
|
||||||
|
build: .
|
||||||
|
container_name: mathtype-to-mathml-prod
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
# 持久化上传文件目录
|
||||||
|
- ./uploads:/app/uploads
|
||||||
|
# 挂载配置文件
|
||||||
|
- ./config:/app/config:ro
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mathtype-prod.rule=Host(`mathtype.local`)"
|
||||||
|
- "traefik.http.services.mathtype-prod.loadbalancer.server.port=3000"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: mathtype-network-prod
|
||||||
37
scripts/mathtype2mathml/docker-compose.yml
Normal file
37
scripts/mathtype2mathml/docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
services:
|
||||||
|
mathtype-converter:
|
||||||
|
build: .
|
||||||
|
container_name: mathtype-to-mathml
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
volumes:
|
||||||
|
# 持久化上传文件目录
|
||||||
|
- ./uploads:/app/uploads
|
||||||
|
# 挂载配置文件
|
||||||
|
- ./config:/app/config:ro
|
||||||
|
# 开发模式:实时同步源代码
|
||||||
|
- ./server_converter.js:/app/server_converter.js
|
||||||
|
- ./server:/app/server
|
||||||
|
- ./public:/app/public
|
||||||
|
- ./vendor:/app/vendor
|
||||||
|
- ./package.json:/app/package.json
|
||||||
|
- ./package-lock.json:/app/package-lock.json
|
||||||
|
- ./entrypoint.sh:/app/entrypoint.sh
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.mathtype.rule=Host(`mathtype.local`)"
|
||||||
|
- "traefik.http.services.mathtype.loadbalancer.server.port=3000"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: mathtype-network
|
||||||
48
scripts/mathtype2mathml/entrypoint.sh
Normal file
48
scripts/mathtype2mathml/entrypoint.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Starting MathType to MathML Converter..."
|
||||||
|
|
||||||
|
# 检查必要的目录
|
||||||
|
echo "📁 Checking directories..."
|
||||||
|
mkdir -p uploads
|
||||||
|
mkdir -p public
|
||||||
|
mkdir -p vendor
|
||||||
|
|
||||||
|
# 确保上传目录权限正确
|
||||||
|
chmod 755 uploads
|
||||||
|
|
||||||
|
# 检查 Ruby 和 gem 是否可用
|
||||||
|
echo "💎 Checking Ruby environment..."
|
||||||
|
ruby --version
|
||||||
|
bundle --version
|
||||||
|
|
||||||
|
# 检查关键 gem 是否安装
|
||||||
|
echo "🔍 Verifying gem installation..."
|
||||||
|
if ! bundle exec ruby -e "require 'mathtype_to_mathml'; puts 'mathtype_to_mathml gem loaded successfully'"; then
|
||||||
|
echo "❌ mathtype_to_mathml gem not found!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 Node.js 环境
|
||||||
|
echo "📦 Checking Node.js environment..."
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
# 检查关键文件是否存在
|
||||||
|
echo "📄 Checking critical files..."
|
||||||
|
for file in "server_converter.js" "server/converter.rb" "public/mathtype_converter_demo.html"; do
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
echo "❌ Critical file missing: $file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Environment check completed successfully!"
|
||||||
|
|
||||||
|
# 创建健康检查端点(如果 server_converter.js 没有的话)
|
||||||
|
echo "🏥 Setting up health check..."
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
echo "🎯 Starting application..."
|
||||||
|
exec "$@"
|
||||||
43
scripts/mathtype2mathml/install/JBY-README.md
Normal file
43
scripts/mathtype2mathml/install/JBY-README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# 本地开发环境配置
|
||||||
|
1. 安装ruby
|
||||||
|
2. 安装依赖包
|
||||||
|
```
|
||||||
|
进入到jby-office-formula目录下
|
||||||
|
bundle install
|
||||||
|
```
|
||||||
|
3. 测试
|
||||||
|
```
|
||||||
|
ruby batch_converter.rb '{"testkey":"C:\\embeddings\\oleObject1.bin"}'
|
||||||
|
ruby batch_converter.rb '{"testkey":"/opt/embeddings/oleObject1.bin"}'
|
||||||
|
ruby batch_converter.rb '{"testkey":"install/example.bin"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
# 线上环境配置
|
||||||
|
1. ruby环境基础镜像(bjjby-registry-vpc.cn-zhangjiakou.cr.aliyuncs.com/base/image:ruby-v1)核心命令
|
||||||
|
安装包: install/ruby-2.6.0.tar.gz、install/gems/*
|
||||||
|
```
|
||||||
|
cd /opt
|
||||||
|
&& tar -zxf ruby-2.6.0.tar.gz
|
||||||
|
&& cd /opt/ruby-2.6.0
|
||||||
|
&& ./configure --prefix=/usr/local/ruby
|
||||||
|
&& make -j$(nproc)
|
||||||
|
&& make install
|
||||||
|
&& export PATH=/usr/local/ruby/bin:$PATH
|
||||||
|
&& gem --version
|
||||||
|
&& unzip /opt/gems.zip -d /opt/
|
||||||
|
&& gem install --local /opt/gems/one/*
|
||||||
|
&& gem install --local /opt/gems/two/*
|
||||||
|
&& make clean
|
||||||
|
&& rm -rf /opt/ruby/ruby-2.6.0
|
||||||
|
&& rm -rf /opt/gems
|
||||||
|
&& rm -rf /var/cache/*
|
||||||
|
```
|
||||||
|
2. 业务容器镜像Dockerfile
|
||||||
|
```
|
||||||
|
FROM bjjby-registry-vpc.cn-zhangjiakou.cr.aliyuncs.com/base/image:ruby-v1
|
||||||
|
LABEL maintainer="chen 15662138562@163.com"
|
||||||
|
RUN mkdir /opt/servers
|
||||||
|
COPY jby-office-formula /opt/servers/jby-office-formula
|
||||||
|
ADD jby-office-file-server/target/jby-office-file-server-3.0.jar /opt/servers/office-file.jar
|
||||||
|
CMD ["java","-jar", "/opt/servers/office-file.jar"]
|
||||||
|
```
|
||||||
BIN
scripts/mathtype2mathml/install/example.bin
Normal file
BIN
scripts/mathtype2mathml/install/example.bin
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/one/coderay-1.1.3.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/one/coderay-1.1.3.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/one/method_source-1.1.0.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/one/method_source-1.1.0.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/one/racc-1.8.1.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/one/racc-1.8.1.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/third/parallel-1.19.2.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/third/parallel-1.19.2.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/two/bindata-2.5.1.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/two/bindata-2.5.1.gem
Normal file
Binary file not shown.
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/two/mini_portile2-2.8.9.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/two/mini_portile2-2.8.9.gem
Normal file
Binary file not shown.
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/two/pry-0.15.2.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/two/pry-0.15.2.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/two/rake-13.3.0.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/two/rake-13.3.0.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/gems/two/ruby-ole-1.2.13.1.gem
Normal file
BIN
scripts/mathtype2mathml/install/gems/two/ruby-ole-1.2.13.1.gem
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/install/ruby-2.6.0.tar.gz
Normal file
BIN
scripts/mathtype2mathml/install/ruby-2.6.0.tar.gz
Normal file
Binary file not shown.
1397
scripts/mathtype2mathml/package-lock.json
generated
Normal file
1397
scripts/mathtype2mathml/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
scripts/mathtype2mathml/package.json
Normal file
32
scripts/mathtype2mathml/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "mathtype-to-mathml-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Server for converting MathType binary files to MathML using Ruby gem",
|
||||||
|
"main": "server_converter.js",
|
||||||
|
"scripts": {
|
||||||
|
"setup": "node server/setup.js",
|
||||||
|
"start": "node server_converter.js",
|
||||||
|
"dev": "nodemon server_converter.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"mathtype",
|
||||||
|
"mathml",
|
||||||
|
"equation",
|
||||||
|
"converter",
|
||||||
|
"ruby",
|
||||||
|
"gem"
|
||||||
|
],
|
||||||
|
"author": "Your Name",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"multer": "^1.4.5-lts.1",
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
891
scripts/mathtype2mathml/public/mathtype_converter_demo.html
Normal file
891
scripts/mathtype2mathml/public/mathtype_converter_demo.html
Normal file
@@ -0,0 +1,891 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>MathType to MathML Converter Demo</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section {
|
||||||
|
border: 2px dashed #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section:hover {
|
||||||
|
border-color: #007bff;
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-panel {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-panel h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathml-output {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mathml-preview {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
min-height: 100px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.success {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.error {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status.info {
|
||||||
|
background-color: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.architecture-diagram {
|
||||||
|
text-align: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-upload-zone {
|
||||||
|
border: 2px dashed #007bff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
margin: 20px 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-upload-zone:hover {
|
||||||
|
border-color: #0056b3;
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-upload-zone.dragover {
|
||||||
|
border-color: #28a745;
|
||||||
|
background-color: #d4edda;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
margin-top: 15px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
background: white;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item .remove-file {
|
||||||
|
color: #dc3545;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-container {
|
||||||
|
margin: 20px 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #007bff;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-results {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item.success {
|
||||||
|
border-left: 4px solid #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-item.error {
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-header {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-mathml {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.output-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>🧮 MathType to MathML Converter</h1>
|
||||||
|
<p class="subtitle">JavaScript implementation for converting MathType binary files to MathML format</p>
|
||||||
|
|
||||||
|
<div class="architecture-diagram">
|
||||||
|
<strong>Conversion Pipeline:</strong><br>
|
||||||
|
Binary MTEF → XML Structure → Preprocessing → MathML Transform → Final Output
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-section">
|
||||||
|
<h3>📁 Upload Single MathType File</h3>
|
||||||
|
<p>Select a MathType binary file (.bin) to convert to MathML</p>
|
||||||
|
<div class="status info" style="margin: 10px 0; font-size: 14px;">
|
||||||
|
<strong>💡 For Real Conversion:</strong> Start the server with <code>npm start</code> to convert actual .bin files.<br>
|
||||||
|
<strong>🎮 Demo Mode:</strong> Works without server but only shows sample equation (x²).
|
||||||
|
</div>
|
||||||
|
<input type="file" id="fileInput" accept=".bin" />
|
||||||
|
<br>
|
||||||
|
<button id="convertBtn" onclick="convertFile()">Convert Real File (Server Required)</button>
|
||||||
|
<button id="demoBtn" onclick="runDemo()">Run Client Demo (x² Sample)</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-section">
|
||||||
|
<h3>📁 Batch Upload MathType Files</h3>
|
||||||
|
<p>Upload multiple MathType binary files (.bin) for batch conversion</p>
|
||||||
|
<div class="status info" style="margin: 10px 0; font-size: 14px;">
|
||||||
|
<strong>🚀 High Performance:</strong> Process multiple files in parallel using optimized batch processing.<br>
|
||||||
|
<strong>📊 Progress Tracking:</strong> Real-time progress updates for each file in the batch.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="batch-upload-zone" id="batchUploadZone" onclick="document.getElementById('batchFileInput').click()">
|
||||||
|
<div>
|
||||||
|
<h4>🎯 Drop files here or click to select</h4>
|
||||||
|
<p>Drag and drop multiple .bin files, or click to browse</p>
|
||||||
|
<p style="font-size: 14px; color: #6c757d;">Supports up to 50 files per batch</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="file" id="batchFileInput" accept=".bin" multiple style="display: none;" />
|
||||||
|
|
||||||
|
<div class="file-list" id="fileList" style="display: none;">
|
||||||
|
<h4>Selected Files:</h4>
|
||||||
|
<div id="fileItems"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 15px;">
|
||||||
|
<button id="batchConvertBtn" onclick="convertBatchFiles()" style="display: none;">
|
||||||
|
🚀 Convert Batch (Server Required)
|
||||||
|
</button>
|
||||||
|
<button id="clearBatchBtn" onclick="clearBatchFiles()" style="display: none; margin-left: 10px;">
|
||||||
|
🗑️ Clear All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress-container" id="progressContainer">
|
||||||
|
<h4>Conversion Progress:</h4>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-bar-fill" id="progressBarFill"></div>
|
||||||
|
</div>
|
||||||
|
<p id="progressText">0 / 0 files processed</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="batch-results" id="batchResults">
|
||||||
|
<h4>Conversion Results:</h4>
|
||||||
|
<div id="resultItems"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="status"></div>
|
||||||
|
|
||||||
|
<div class="output-section">
|
||||||
|
<div class="output-panel">
|
||||||
|
<h3>📄 Generated MathML</h3>
|
||||||
|
<div id="mathmlOutput" class="mathml-output">
|
||||||
|
<!-- Generated MathML will appear here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="output-panel">
|
||||||
|
<h3>🔍 MathML Preview</h3>
|
||||||
|
<div id="mathmlPreview" class="mathml-preview">
|
||||||
|
<!-- Rendered math will appear here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="debugInfo" class="debug-info" style="display: none;">
|
||||||
|
<!-- Debug information will appear here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>🚀 Features</h2>
|
||||||
|
<div class="feature-grid">
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>Multi-Stage Pipeline</h4>
|
||||||
|
<p>Follows the same architecture as the Ruby mathtype_to_mathml gem with binary parsing, preprocessing, and transformation stages.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>Character Mapping</h4>
|
||||||
|
<p>Comprehensive Unicode character support including Greek letters, mathematical operators, and special symbols.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>Mathematical Structures</h4>
|
||||||
|
<p>Supports fractions, subscripts, superscripts, roots, matrices, integrals, summations, and more.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature-card">
|
||||||
|
<h4>Web Compatible</h4>
|
||||||
|
<p>Pure JavaScript implementation that works in browsers without external dependencies.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h2>⚠️ Important Notes</h2>
|
||||||
|
<div class="status info">
|
||||||
|
<strong>Binary Parsing Limitation:</strong> This demo uses a simplified MTEF parser. For production use, you would need:
|
||||||
|
<ul style="margin: 10px 0; padding-left: 20px;">
|
||||||
|
<li>A complete MTEF binary format parser</li>
|
||||||
|
<li>WebAssembly port of the Ruby mathtype gem</li>
|
||||||
|
<li>Server-side conversion with the Ruby gem</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include the converter script -->
|
||||||
|
<script src="mathtype_to_mathml_converter.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Global converter instance
|
||||||
|
let converter;
|
||||||
|
|
||||||
|
// Initialize converter when page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
try {
|
||||||
|
converter = new MathTypeToMathMLConverter();
|
||||||
|
showStatus('Converter initialized successfully', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
showStatus('Failed to initialize converter: ' + error.message, 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// File conversion function - uses server for real binary parsing
|
||||||
|
async function convertFile() {
|
||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
showStatus('Please select a file first', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showStatus('Converting file using server...', 'info');
|
||||||
|
setButtonsEnabled(false);
|
||||||
|
|
||||||
|
// Show debug info about the binary data
|
||||||
|
showDebugInfo(`Uploading file: ${file.name} (${file.size} bytes)`);
|
||||||
|
|
||||||
|
// Send file to server for conversion
|
||||||
|
const mathml = await convertFileOnServer(file);
|
||||||
|
|
||||||
|
// Display results
|
||||||
|
displayResults(mathml);
|
||||||
|
showStatus('Server conversion completed successfully!', 'success');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showStatus('Server conversion failed: ' + error.message, 'error');
|
||||||
|
console.error('Conversion error:', error);
|
||||||
|
|
||||||
|
// Fallback to client-side demo
|
||||||
|
showStatus('Falling back to client-side demo...', 'info');
|
||||||
|
try {
|
||||||
|
const binaryData = await loadBinaryFile(fileInput);
|
||||||
|
const mathml = await converter.convert(binaryData);
|
||||||
|
displayResults(mathml);
|
||||||
|
showStatus('Client-side demo completed (shows sample x² only)', 'success');
|
||||||
|
} catch (fallbackError) {
|
||||||
|
showStatus('Both server and client conversion failed', 'error');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server-side conversion function
|
||||||
|
async function convertFileOnServer(file) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const response = await fetch('/convert-mathtype', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.message || `Server error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Conversion failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.mathml;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo function with sample data
|
||||||
|
async function runDemo() {
|
||||||
|
if (!converter) {
|
||||||
|
showStatus('Converter not initialized', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showStatus('Running demo with sample equation (x²)...', 'info');
|
||||||
|
setButtonsEnabled(false);
|
||||||
|
|
||||||
|
// Create sample binary data (this would normally be read from a .bin file)
|
||||||
|
const sampleData = new Uint8Array([0x01, 0x02, 0x03, 0x04]); // Placeholder binary data
|
||||||
|
|
||||||
|
showDebugInfo('Demo using simulated binary data for equation: x²');
|
||||||
|
|
||||||
|
// Convert using the sample data
|
||||||
|
const mathml = await converter.convert(sampleData);
|
||||||
|
|
||||||
|
// Display results
|
||||||
|
displayResults(mathml);
|
||||||
|
showStatus('Demo completed successfully!', 'success');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showStatus('Demo failed: ' + error.message, 'error');
|
||||||
|
console.error('Demo error:', error);
|
||||||
|
} finally {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display conversion results
|
||||||
|
function displayResults(mathml) {
|
||||||
|
// Show raw MathML
|
||||||
|
document.getElementById('mathmlOutput').textContent = mathml;
|
||||||
|
|
||||||
|
// Try to render MathML preview
|
||||||
|
const previewDiv = document.getElementById('mathmlPreview');
|
||||||
|
try {
|
||||||
|
previewDiv.innerHTML = mathml;
|
||||||
|
|
||||||
|
// If MathJax is available, re-render
|
||||||
|
if (window.MathJax && window.MathJax.typesetPromise) {
|
||||||
|
MathJax.typesetPromise([previewDiv]).catch((err) => {
|
||||||
|
console.warn('MathJax rendering failed:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
previewDiv.innerHTML = '<em>Preview not available (MathML not supported in this browser)</em>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show status message
|
||||||
|
function showStatus(message, type = 'info') {
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
statusDiv.innerHTML = `<div class="status ${type}">${message}</div>`;
|
||||||
|
|
||||||
|
// Auto-hide success/info messages after 5 seconds
|
||||||
|
if (type === 'success' || type === 'info') {
|
||||||
|
setTimeout(() => {
|
||||||
|
statusDiv.innerHTML = '';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show debug information
|
||||||
|
function showDebugInfo(info) {
|
||||||
|
const debugDiv = document.getElementById('debugInfo');
|
||||||
|
debugDiv.style.display = 'block';
|
||||||
|
debugDiv.textContent = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/disable buttons
|
||||||
|
function setButtonsEnabled(enabled) {
|
||||||
|
document.getElementById('convertBtn').disabled = !enabled;
|
||||||
|
document.getElementById('demoBtn').disabled = !enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch processing variables
|
||||||
|
let batchFiles = [];
|
||||||
|
|
||||||
|
// Initialize batch upload functionality
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const batchUploadZone = document.getElementById('batchUploadZone');
|
||||||
|
const batchFileInput = document.getElementById('batchFileInput');
|
||||||
|
|
||||||
|
// File input change handler
|
||||||
|
batchFileInput.addEventListener('change', function(e) {
|
||||||
|
handleBatchFiles(Array.from(e.target.files));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag and drop handlers for batch upload
|
||||||
|
batchUploadZone.addEventListener('dragover', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
batchUploadZone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
batchUploadZone.addEventListener('dragleave', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
batchUploadZone.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
batchUploadZone.addEventListener('drop', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
batchUploadZone.classList.remove('dragover');
|
||||||
|
|
||||||
|
const files = Array.from(e.dataTransfer.files).filter(file =>
|
||||||
|
file.name.toLowerCase().endsWith('.bin')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
handleBatchFiles(files);
|
||||||
|
} else {
|
||||||
|
showStatus('No valid .bin files found in the dropped files', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle batch file selection
|
||||||
|
function handleBatchFiles(files) {
|
||||||
|
// Filter for .bin files and check for duplicates
|
||||||
|
const validFiles = files.filter(file => {
|
||||||
|
if (!file.name.toLowerCase().endsWith('.bin')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check for duplicates
|
||||||
|
return !batchFiles.some(existing =>
|
||||||
|
existing.name === file.name && existing.size === file.size
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validFiles.length === 0) {
|
||||||
|
showStatus('No new valid .bin files to add', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check total file limit
|
||||||
|
if (batchFiles.length + validFiles.length > 50) {
|
||||||
|
showStatus('Maximum 50 files allowed per batch', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add files to batch
|
||||||
|
batchFiles.push(...validFiles);
|
||||||
|
updateFileList();
|
||||||
|
showStatus(`Added ${validFiles.length} file(s) to batch (${batchFiles.length} total)`, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update file list display
|
||||||
|
function updateFileList() {
|
||||||
|
const fileList = document.getElementById('fileList');
|
||||||
|
const fileItems = document.getElementById('fileItems');
|
||||||
|
const batchConvertBtn = document.getElementById('batchConvertBtn');
|
||||||
|
const clearBatchBtn = document.getElementById('clearBatchBtn');
|
||||||
|
|
||||||
|
if (batchFiles.length === 0) {
|
||||||
|
fileList.style.display = 'none';
|
||||||
|
batchConvertBtn.style.display = 'none';
|
||||||
|
clearBatchBtn.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileList.style.display = 'block';
|
||||||
|
batchConvertBtn.style.display = 'inline-block';
|
||||||
|
clearBatchBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
|
fileItems.innerHTML = batchFiles.map((file, index) => `
|
||||||
|
<div class="file-item">
|
||||||
|
<span>${file.name} (${formatFileSize(file.size)})</span>
|
||||||
|
<span class="remove-file" onclick="removeFile(${index})">✕</span>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove file from batch
|
||||||
|
function removeFile(index) {
|
||||||
|
batchFiles.splice(index, 1);
|
||||||
|
updateFileList();
|
||||||
|
showStatus('File removed from batch', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all batch files
|
||||||
|
function clearBatchFiles() {
|
||||||
|
batchFiles = [];
|
||||||
|
updateFileList();
|
||||||
|
document.getElementById('progressContainer').style.display = 'none';
|
||||||
|
document.getElementById('batchResults').style.display = 'none';
|
||||||
|
showStatus('Batch cleared', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert batch files
|
||||||
|
async function convertBatchFiles() {
|
||||||
|
if (batchFiles.length === 0) {
|
||||||
|
showStatus('No files selected for batch conversion', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
showStatus(`Starting batch conversion of ${batchFiles.length} files...`, 'info');
|
||||||
|
|
||||||
|
// Show progress container
|
||||||
|
const progressContainer = document.getElementById('progressContainer');
|
||||||
|
const progressBarFill = document.getElementById('progressBarFill');
|
||||||
|
const progressText = document.getElementById('progressText');
|
||||||
|
const batchResults = document.getElementById('batchResults');
|
||||||
|
const resultItems = document.getElementById('resultItems');
|
||||||
|
|
||||||
|
progressContainer.style.display = 'block';
|
||||||
|
batchResults.style.display = 'block';
|
||||||
|
resultItems.innerHTML = '';
|
||||||
|
|
||||||
|
// Update progress
|
||||||
|
progressBarFill.style.width = '0%';
|
||||||
|
progressText.textContent = `0 / ${batchFiles.length} files processed`;
|
||||||
|
|
||||||
|
// Disable buttons
|
||||||
|
document.getElementById('batchConvertBtn').disabled = true;
|
||||||
|
document.getElementById('clearBatchBtn').disabled = true;
|
||||||
|
|
||||||
|
// Send files to server for batch conversion
|
||||||
|
const results = await convertBatchOnServer(batchFiles);
|
||||||
|
|
||||||
|
// Update progress to 100%
|
||||||
|
progressBarFill.style.width = '100%';
|
||||||
|
progressText.textContent = `${batchFiles.length} / ${batchFiles.length} files processed`;
|
||||||
|
|
||||||
|
// Display results
|
||||||
|
displayBatchResults(results);
|
||||||
|
|
||||||
|
const successCount = results.results.filter(r => r.success).length;
|
||||||
|
const failCount = results.results.length - successCount;
|
||||||
|
|
||||||
|
showStatus(
|
||||||
|
`Batch conversion completed! ${successCount} successful, ${failCount} failed`,
|
||||||
|
failCount === 0 ? 'success' : 'info'
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
showStatus('Batch conversion failed: ' + error.message, 'error');
|
||||||
|
console.error('Batch conversion error:', error);
|
||||||
|
} finally {
|
||||||
|
// Re-enable buttons
|
||||||
|
document.getElementById('batchConvertBtn').disabled = false;
|
||||||
|
document.getElementById('clearBatchBtn').disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server-side batch conversion function
|
||||||
|
async function convertBatchOnServer(files) {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Append all files
|
||||||
|
files.forEach(file => {
|
||||||
|
formData.append('files', file);
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch('/convert-mathtype-batch', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.message || `Server error: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Batch conversion failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display batch conversion results
|
||||||
|
function displayBatchResults(results) {
|
||||||
|
const resultItems = document.getElementById('resultItems');
|
||||||
|
|
||||||
|
resultItems.innerHTML = results.results.map((result, index) => {
|
||||||
|
const statusClass = result.success ? 'success' : 'error';
|
||||||
|
const statusIcon = result.success ? '✅' : '❌';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="result-item ${statusClass}">
|
||||||
|
<div class="result-header">
|
||||||
|
<span>${statusIcon} ${result.originalName}</span>
|
||||||
|
<span>${result.success ? 'Success' : 'Failed'}</span>
|
||||||
|
</div>
|
||||||
|
${result.error ? `<div style="color: #dc3545; margin-bottom: 10px;">Error: ${result.error}</div>` : ''}
|
||||||
|
${result.success && result.mathml ? `
|
||||||
|
<div class="result-mathml">${result.mathml}</div>
|
||||||
|
<button onclick="copyToClipboard('${escapeHtml(result.mathml)}')" style="margin-top: 10px;">
|
||||||
|
📋 Copy MathML
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
navigator.clipboard.writeText(text.replace(/'/g, "'")).then(() => {
|
||||||
|
showStatus('MathML copied to clipboard', 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to copy: ', err);
|
||||||
|
showStatus('Failed to copy to clipboard', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// File drop functionality
|
||||||
|
const uploadSection = document.querySelector('.upload-section');
|
||||||
|
|
||||||
|
uploadSection.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadSection.style.borderColor = '#007bff';
|
||||||
|
uploadSection.style.backgroundColor = '#f0f8ff';
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadSection.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadSection.style.borderColor = '#ddd';
|
||||||
|
uploadSection.style.backgroundColor = '#fafafa';
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadSection.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadSection.style.borderColor = '#ddd';
|
||||||
|
uploadSection.style.backgroundColor = '#fafafa';
|
||||||
|
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
if (files.length > 0) {
|
||||||
|
document.getElementById('fileInput').files = files;
|
||||||
|
showStatus('File dropped successfully: ' + files[0].name, 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Optional: Include MathJax for better MathML rendering -->
|
||||||
|
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js"></script>
|
||||||
|
<script>
|
||||||
|
window.MathJax = {
|
||||||
|
startup: {
|
||||||
|
ready: () => {
|
||||||
|
console.log('MathJax is loaded and ready');
|
||||||
|
MathJax.startup.defaultReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
766
scripts/mathtype2mathml/public/mathtype_to_mathml_converter.js
Normal file
766
scripts/mathtype2mathml/public/mathtype_to_mathml_converter.js
Normal file
@@ -0,0 +1,766 @@
|
|||||||
|
/**
|
||||||
|
* MathType to MathML Converter (JavaScript Implementation)
|
||||||
|
*
|
||||||
|
* This JavaScript implementation follows the same architectural principles
|
||||||
|
* as the Ruby mathtype_to_mathml gem, providing a multi-stage conversion
|
||||||
|
* pipeline from MathType binary format to MathML.
|
||||||
|
*
|
||||||
|
* Architecture:
|
||||||
|
* Binary MTEF → XML Structure → Preprocessing → XSLT-like Transform → MathML
|
||||||
|
*/
|
||||||
|
|
||||||
|
class MathTypeToMathMLConverter {
|
||||||
|
constructor() {
|
||||||
|
this.characterReplacer = new CharacterReplacer();
|
||||||
|
this.mover = new ElementMover();
|
||||||
|
this.transformer = new MathMLTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert MathType binary data to MathML
|
||||||
|
* @param {ArrayBuffer|Uint8Array} binaryData - MathType binary data
|
||||||
|
* @returns {Promise<string>} MathML string
|
||||||
|
*/
|
||||||
|
async convert(binaryData) {
|
||||||
|
try {
|
||||||
|
// Stage 1: Parse binary MTEF to XML structure
|
||||||
|
const xmlStructure = await this.parseBinaryMTEF(binaryData);
|
||||||
|
|
||||||
|
// Stage 2: Preprocessing
|
||||||
|
this.mover.restructureElements(xmlStructure);
|
||||||
|
this.characterReplacer.replaceCharacters(xmlStructure);
|
||||||
|
|
||||||
|
// Stage 3: Transform to MathML
|
||||||
|
const mathml = this.transformer.transformToMathML(xmlStructure);
|
||||||
|
|
||||||
|
// Stage 4: Cleanup and namespace handling
|
||||||
|
return this.finalizeMathML(mathml);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`MathType conversion failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse binary MTEF data to XML-like structure
|
||||||
|
* Note: This is a simplified implementation. In practice, you would need
|
||||||
|
* a full MTEF parser or use a WebAssembly port of the mathtype gem.
|
||||||
|
*/
|
||||||
|
async parseBinaryMTEF(binaryData) {
|
||||||
|
// For demo purposes, we'll simulate the XML structure
|
||||||
|
// In a real implementation, this would parse the actual MTEF binary format
|
||||||
|
|
||||||
|
if (binaryData instanceof ArrayBuffer) {
|
||||||
|
binaryData = new Uint8Array(binaryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Placeholder: In reality, this would parse the binary MTEF format
|
||||||
|
// You could use a WebAssembly port of the Ruby mathtype gem
|
||||||
|
// or implement a native JavaScript MTEF parser
|
||||||
|
|
||||||
|
return this.createSampleXMLStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a sample XML structure for demonstration
|
||||||
|
* This would be replaced by actual MTEF parsing
|
||||||
|
*/
|
||||||
|
createSampleXMLStructure() {
|
||||||
|
return {
|
||||||
|
type: 'mtef',
|
||||||
|
equation_options: 'inline',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'slot',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'char',
|
||||||
|
mt_code_value: '0x0078', // 'x'
|
||||||
|
typeface: '1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'tmpl',
|
||||||
|
selector: 'tmSUP',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'slot',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'char',
|
||||||
|
mt_code_value: '0x0032', // '2'
|
||||||
|
typeface: '2'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finalize MathML output with proper namespace
|
||||||
|
*/
|
||||||
|
finalizeMathML(mathmlContent) {
|
||||||
|
return `<math xmlns="http://www.w3.org/1998/Math/MathML">${mathmlContent}</math>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Character Replacement Module
|
||||||
|
* Handles Unicode character conversion and mathematical symbol mapping
|
||||||
|
*/
|
||||||
|
class CharacterReplacer {
|
||||||
|
constructor() {
|
||||||
|
this.replacements = this.initializeReplacements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize character replacement mappings
|
||||||
|
* Based on the Ruby CharReplacer class
|
||||||
|
*/
|
||||||
|
initializeReplacements() {
|
||||||
|
return {
|
||||||
|
// Basic Latin digits
|
||||||
|
'0x0030': { mathmode: '<mn>0</mn>', textmode: '0' },
|
||||||
|
'0x0031': { mathmode: '<mn>1</mn>', textmode: '1' },
|
||||||
|
'0x0032': { mathmode: '<mn>2</mn>', textmode: '2' },
|
||||||
|
'0x0033': { mathmode: '<mn>3</mn>', textmode: '3' },
|
||||||
|
'0x0034': { mathmode: '<mn>4</mn>', textmode: '4' },
|
||||||
|
'0x0035': { mathmode: '<mn>5</mn>', textmode: '5' },
|
||||||
|
'0x0036': { mathmode: '<mn>6</mn>', textmode: '6' },
|
||||||
|
'0x0037': { mathmode: '<mn>7</mn>', textmode: '7' },
|
||||||
|
'0x0038': { mathmode: '<mn>8</mn>', textmode: '8' },
|
||||||
|
'0x0039': { mathmode: '<mn>9</mn>', textmode: '9' },
|
||||||
|
|
||||||
|
// Basic Latin letters (uppercase)
|
||||||
|
'0x0041': { mathmode: '<mi>A</mi>', textmode: 'A' },
|
||||||
|
'0x0042': { mathmode: '<mi>B</mi>', textmode: 'B' },
|
||||||
|
'0x0043': { mathmode: '<mi>C</mi>', textmode: 'C' },
|
||||||
|
'0x0044': { mathmode: '<mi>D</mi>', textmode: 'D' },
|
||||||
|
'0x0045': { mathmode: '<mi>E</mi>', textmode: 'E' },
|
||||||
|
'0x0046': { mathmode: '<mi>F</mi>', textmode: 'F' },
|
||||||
|
|
||||||
|
// Basic Latin letters (lowercase)
|
||||||
|
'0x0061': { mathmode: '<mi>a</mi>', textmode: 'a' },
|
||||||
|
'0x0062': { mathmode: '<mi>b</mi>', textmode: 'b' },
|
||||||
|
'0x0063': { mathmode: '<mi>c</mi>', textmode: 'c' },
|
||||||
|
'0x0064': { mathmode: '<mi>d</mi>', textmode: 'd' },
|
||||||
|
'0x0065': { mathmode: '<mi>e</mi>', textmode: 'e' },
|
||||||
|
'0x0066': { mathmode: '<mi>f</mi>', textmode: 'f' },
|
||||||
|
'0x0067': { mathmode: '<mi>g</mi>', textmode: 'g' },
|
||||||
|
'0x0068': { mathmode: '<mi>h</mi>', textmode: 'h' },
|
||||||
|
'0x0069': { mathmode: '<mi>i</mi>', textmode: 'i' },
|
||||||
|
'0x006A': { mathmode: '<mi>j</mi>', textmode: 'j' },
|
||||||
|
'0x006B': { mathmode: '<mi>k</mi>', textmode: 'k' },
|
||||||
|
'0x006C': { mathmode: '<mi>l</mi>', textmode: 'l' },
|
||||||
|
'0x006D': { mathmode: '<mi>m</mi>', textmode: 'm' },
|
||||||
|
'0x006E': { mathmode: '<mi>n</mi>', textmode: 'n' },
|
||||||
|
'0x006F': { mathmode: '<mi>o</mi>', textmode: 'o' },
|
||||||
|
'0x0070': { mathmode: '<mi>p</mi>', textmode: 'p' },
|
||||||
|
'0x0071': { mathmode: '<mi>q</mi>', textmode: 'q' },
|
||||||
|
'0x0072': { mathmode: '<mi>r</mi>', textmode: 'r' },
|
||||||
|
'0x0073': { mathmode: '<mi>s</mi>', textmode: 's' },
|
||||||
|
'0x0074': { mathmode: '<mi>t</mi>', textmode: 't' },
|
||||||
|
'0x0075': { mathmode: '<mi>u</mi>', textmode: 'u' },
|
||||||
|
'0x0076': { mathmode: '<mi>v</mi>', textmode: 'v' },
|
||||||
|
'0x0077': { mathmode: '<mi>w</mi>', textmode: 'w' },
|
||||||
|
'0x0078': { mathmode: '<mi>x</mi>', textmode: 'x' },
|
||||||
|
'0x0079': { mathmode: '<mi>y</mi>', textmode: 'y' },
|
||||||
|
'0x007A': { mathmode: '<mi>z</mi>', textmode: 'z' },
|
||||||
|
|
||||||
|
// Mathematical operators
|
||||||
|
'0x002B': { mathmode: '<mo>+</mo>', textmode: '+' },
|
||||||
|
'0x002D': { mathmode: '<mo>-</mo>', textmode: '-' },
|
||||||
|
'0x002A': { mathmode: '<mo>*</mo>', textmode: '*' },
|
||||||
|
'0x002F': { mathmode: '<mo>/</mo>', textmode: '/' },
|
||||||
|
'0x003D': { mathmode: '<mo>=</mo>', textmode: '=' },
|
||||||
|
'0x003C': { mathmode: '<mo><</mo>', textmode: '<' },
|
||||||
|
'0x003E': { mathmode: '<mo>></mo>', textmode: '>' },
|
||||||
|
|
||||||
|
// Parentheses and brackets
|
||||||
|
'0x0028': { mathmode: '<mo stretchy="false">(</mo>', textmode: '(' },
|
||||||
|
'0x0029': { mathmode: '<mo stretchy="false">)</mo>', textmode: ')' },
|
||||||
|
'0x005B': { mathmode: '<mo stretchy="false">[</mo>', textmode: '[' },
|
||||||
|
'0x005D': { mathmode: '<mo stretchy="false">]</mo>', textmode: ']' },
|
||||||
|
'0x007B': { mathmode: '<mo>{</mo>', textmode: '{' },
|
||||||
|
'0x007D': { mathmode: '<mo>}</mo>', textmode: '}' },
|
||||||
|
|
||||||
|
// Greek letters (common mathematical symbols)
|
||||||
|
'0x03B1': { mathmode: '<mi>α</mi>', textmode: 'α' }, // alpha
|
||||||
|
'0x03B2': { mathmode: '<mi>β</mi>', textmode: 'β' }, // beta
|
||||||
|
'0x03B3': { mathmode: '<mi>γ</mi>', textmode: 'γ' }, // gamma
|
||||||
|
'0x03B4': { mathmode: '<mi>δ</mi>', textmode: 'δ' }, // delta
|
||||||
|
'0x03B5': { mathmode: '<mi>ε</mi>', textmode: 'ε' }, // epsilon
|
||||||
|
'0x03B8': { mathmode: '<mi>θ</mi>', textmode: 'θ' }, // theta
|
||||||
|
'0x03BB': { mathmode: '<mi>λ</mi>', textmode: 'λ' }, // lambda
|
||||||
|
'0x03BC': { mathmode: '<mi>μ</mi>', textmode: 'μ' }, // mu
|
||||||
|
'0x03C0': { mathmode: '<mi>π</mi>', textmode: 'π' }, // pi
|
||||||
|
'0x03C3': { mathmode: '<mi>σ</mi>', textmode: 'σ' }, // sigma
|
||||||
|
'0x03C4': { mathmode: '<mi>τ</mi>', textmode: 'τ' }, // tau
|
||||||
|
'0x03C6': { mathmode: '<mi>φ</mi>', textmode: 'φ' }, // phi
|
||||||
|
'0x03C7': { mathmode: '<mi>χ</mi>', textmode: 'χ' }, // chi
|
||||||
|
'0x03C8': { mathmode: '<mi>ψ</mi>', textmode: 'ψ' }, // psi
|
||||||
|
'0x03C9': { mathmode: '<mi>ω</mi>', textmode: 'ω' }, // omega
|
||||||
|
|
||||||
|
// Mathematical symbols
|
||||||
|
'0x221E': { mathmode: '<mo>∞</mo>', textmode: '∞' }, // infinity
|
||||||
|
'0x2211': { mathmode: '<mo>∑</mo>', textmode: '∑' }, // summation
|
||||||
|
'0x222B': { mathmode: '<mo>∫</mo>', textmode: '∫' }, // integral
|
||||||
|
'0x2206': { mathmode: '<mo>∆</mo>', textmode: '∆' }, // delta
|
||||||
|
'0x2207': { mathmode: '<mo>∇</mo>', textmode: '∇' }, // nabla
|
||||||
|
'0x2208': { mathmode: '<mo>∈</mo>', textmode: '∈' }, // element of
|
||||||
|
'0x2209': { mathmode: '<mo>∉</mo>', textmode: '∉' }, // not element of
|
||||||
|
'0x220B': { mathmode: '<mo>∋</mo>', textmode: '∋' }, // contains
|
||||||
|
'0x2260': { mathmode: '<mo>≠</mo>', textmode: '≠' }, // not equal
|
||||||
|
'0x2264': { mathmode: '<mo>≤</mo>', textmode: '≤' }, // less than or equal
|
||||||
|
'0x2265': { mathmode: '<mo>≥</mo>', textmode: '≥' }, // greater than or equal
|
||||||
|
'0x00B1': { mathmode: '<mo>±</mo>', textmode: '±' }, // plus-minus
|
||||||
|
'0x00D7': { mathmode: '<mo>×</mo>', textmode: '×' }, // multiplication
|
||||||
|
'0x00F7': { mathmode: '<mo>÷</mo>', textmode: '÷' }, // division
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace characters in the XML structure
|
||||||
|
*/
|
||||||
|
replaceCharacters(xmlStructure) {
|
||||||
|
this.traverseAndReplace(xmlStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively traverse and replace characters
|
||||||
|
*/
|
||||||
|
traverseAndReplace(node) {
|
||||||
|
if (node.type === 'char') {
|
||||||
|
const replacement = this.getCharacterReplacement(node);
|
||||||
|
if (replacement) {
|
||||||
|
node.mathml = replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach(child => this.traverseAndReplace(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get character replacement based on mt_code_value
|
||||||
|
*/
|
||||||
|
getCharacterReplacement(charNode) {
|
||||||
|
const codeValue = charNode.mt_code_value;
|
||||||
|
const isTextMode = charNode.variation === 'textmode';
|
||||||
|
const replacement = this.replacements[codeValue];
|
||||||
|
|
||||||
|
if (!replacement) {
|
||||||
|
// Handle character ranges
|
||||||
|
return this.handleCharacterRange(codeValue, isTextMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isTextMode ? replacement.textmode : replacement.mathmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle character ranges for undefined individual characters
|
||||||
|
*/
|
||||||
|
handleCharacterRange(codeValue, isTextMode) {
|
||||||
|
const codeNum = parseInt(codeValue, 16);
|
||||||
|
|
||||||
|
// Numbers (0-9)
|
||||||
|
if (codeNum >= 0x0030 && codeNum <= 0x0039) {
|
||||||
|
const char = String.fromCharCode(codeNum);
|
||||||
|
return isTextMode ? char : `<mn>${char}</mn>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uppercase letters (A-Z)
|
||||||
|
if (codeNum >= 0x0041 && codeNum <= 0x005A) {
|
||||||
|
const char = String.fromCharCode(codeNum);
|
||||||
|
return isTextMode ? char : `<mi>${char}</mi>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lowercase letters (a-z)
|
||||||
|
if (codeNum >= 0x0061 && codeNum <= 0x007A) {
|
||||||
|
const char = String.fromCharCode(codeNum);
|
||||||
|
return isTextMode ? char : `<mi>${char}</mi>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mathematical operators range
|
||||||
|
if (codeNum >= 0x2200 && codeNum <= 0x22FF) {
|
||||||
|
const char = String.fromCharCode(codeNum);
|
||||||
|
return isTextMode ? char : `<mo>${char}</mo>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
try {
|
||||||
|
const char = String.fromCharCode(codeNum);
|
||||||
|
return isTextMode ? char : `<mi>${char}</mi>`;
|
||||||
|
} catch (e) {
|
||||||
|
return 'Unsupported character';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element Mover Module
|
||||||
|
* Handles structural reorganization for subscripts/superscripts
|
||||||
|
*/
|
||||||
|
class ElementMover {
|
||||||
|
constructor() {
|
||||||
|
this.subscriptSelectors = ['tmSUP', 'tmSUB', 'tmSUBSUP'];
|
||||||
|
this.parenthesesPairs = {
|
||||||
|
'0x0028': '0x0029', // ( )
|
||||||
|
'0x005B': '0x005D', // [ ]
|
||||||
|
'0x007B': '0x007D', // { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restructure elements to handle MathType vs MathML differences
|
||||||
|
*/
|
||||||
|
restructureElements(xmlStructure) {
|
||||||
|
this.moveSubscriptsAndSuperscripts(xmlStructure);
|
||||||
|
this.invertEmbellishments(xmlStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move base elements into subscript/superscript containers
|
||||||
|
*/
|
||||||
|
moveSubscriptsAndSuperscripts(node) {
|
||||||
|
if (node.children) {
|
||||||
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
const child = node.children[i];
|
||||||
|
|
||||||
|
if (child.type === 'tmpl' && this.subscriptSelectors.includes(child.selector)) {
|
||||||
|
// Find preceding element(s) to use as base
|
||||||
|
const baseElements = this.findBaseElements(node.children, i);
|
||||||
|
if (baseElements.length > 0) {
|
||||||
|
// Create new slot for base elements
|
||||||
|
const baseSlot = {
|
||||||
|
type: 'slot',
|
||||||
|
children: baseElements
|
||||||
|
};
|
||||||
|
|
||||||
|
// Insert base slot as first child of template
|
||||||
|
child.children = child.children || [];
|
||||||
|
child.children.unshift(baseSlot);
|
||||||
|
|
||||||
|
// Remove base elements from original position
|
||||||
|
baseElements.forEach(elem => {
|
||||||
|
const index = node.children.indexOf(elem);
|
||||||
|
if (index > -1) {
|
||||||
|
node.children.splice(index, 1);
|
||||||
|
if (index < i) i--; // Adjust loop index
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively process children
|
||||||
|
this.moveSubscriptsAndSuperscripts(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find base elements for subscript/superscript
|
||||||
|
*/
|
||||||
|
findBaseElements(siblings, templateIndex) {
|
||||||
|
const baseElements = [];
|
||||||
|
|
||||||
|
// Look for preceding elements
|
||||||
|
for (let i = templateIndex - 1; i >= 0; i--) {
|
||||||
|
const sibling = siblings[i];
|
||||||
|
|
||||||
|
// For simple cases, take the immediately preceding element
|
||||||
|
if (sibling.type === 'char' ||
|
||||||
|
(sibling.type === 'tmpl' && !this.subscriptSelectors.includes(sibling.selector))) {
|
||||||
|
baseElements.unshift(sibling);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invert embellishment structures: char[embell] -> embell[char]
|
||||||
|
*/
|
||||||
|
invertEmbellishments(node) {
|
||||||
|
if (node.type === 'char' && node.embellishment) {
|
||||||
|
// Move char content into embellishment
|
||||||
|
const embellishment = node.embellishment;
|
||||||
|
embellishment.children = embellishment.children || [];
|
||||||
|
embellishment.children.push({
|
||||||
|
type: 'char',
|
||||||
|
mt_code_value: node.mt_code_value,
|
||||||
|
typeface: node.typeface
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace char node with embellishment
|
||||||
|
Object.keys(node).forEach(key => delete node[key]);
|
||||||
|
Object.assign(node, embellishment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
node.children.forEach(child => this.invertEmbellishments(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MathML Transformer Module
|
||||||
|
* Converts preprocessed XML structure to MathML
|
||||||
|
*/
|
||||||
|
class MathMLTransformer {
|
||||||
|
constructor() {
|
||||||
|
this.templateHandlers = this.initializeTemplateHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize template transformation handlers
|
||||||
|
*/
|
||||||
|
initializeTemplateHandlers() {
|
||||||
|
return {
|
||||||
|
// Fractions
|
||||||
|
'tmFRACT': (node) => this.handleFraction(node),
|
||||||
|
|
||||||
|
// Subscripts and superscripts
|
||||||
|
'tmSUP': (node) => this.handleSuperscript(node),
|
||||||
|
'tmSUB': (node) => this.handleSubscript(node),
|
||||||
|
'tmSUBSUP': (node) => this.handleSubSuperscript(node),
|
||||||
|
|
||||||
|
// Roots
|
||||||
|
'tmROOT': (node) => this.handleRoot(node),
|
||||||
|
'tmSQRT': (node) => this.handleSquareRoot(node),
|
||||||
|
|
||||||
|
// Large operators
|
||||||
|
'tmSUM': (node) => this.handleSummation(node),
|
||||||
|
'tmINT': (node) => this.handleIntegral(node),
|
||||||
|
|
||||||
|
// Matrices
|
||||||
|
'tmMATRIX': (node) => this.handleMatrix(node),
|
||||||
|
|
||||||
|
// Fences (parentheses, brackets)
|
||||||
|
'tmPARENS': (node) => this.handleParentheses(node),
|
||||||
|
'tmBRACK': (node) => this.handleBrackets(node),
|
||||||
|
'tmBRACE': (node) => this.handleBraces(node),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform XML structure to MathML
|
||||||
|
*/
|
||||||
|
transformToMathML(xmlStructure) {
|
||||||
|
return this.processNode(xmlStructure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a single node
|
||||||
|
*/
|
||||||
|
processNode(node) {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'mtef':
|
||||||
|
return this.processMTEF(node);
|
||||||
|
case 'slot':
|
||||||
|
return this.processSlot(node);
|
||||||
|
case 'char':
|
||||||
|
return this.processCharacter(node);
|
||||||
|
case 'tmpl':
|
||||||
|
return this.processTemplate(node);
|
||||||
|
default:
|
||||||
|
return this.processChildren(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process MTEF root node
|
||||||
|
*/
|
||||||
|
processMTEF(node) {
|
||||||
|
const content = this.processChildren(node);
|
||||||
|
const displayMode = node.equation_options === 'block' ? ' display="block"' : '';
|
||||||
|
return content; // Math wrapper added in finalizeMathML
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process slot (grouping) node
|
||||||
|
*/
|
||||||
|
processSlot(node) {
|
||||||
|
const content = this.processChildren(node);
|
||||||
|
return content ? `<mrow>${content}</mrow>` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process character node
|
||||||
|
*/
|
||||||
|
processCharacter(node) {
|
||||||
|
return node.mathml || `<mi>${String.fromCharCode(parseInt(node.mt_code_value, 16))}</mi>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process template node
|
||||||
|
*/
|
||||||
|
processTemplate(node) {
|
||||||
|
const handler = this.templateHandlers[node.selector];
|
||||||
|
if (handler) {
|
||||||
|
return handler(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default processing for unknown templates
|
||||||
|
return this.processChildren(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process children nodes
|
||||||
|
*/
|
||||||
|
processChildren(node) {
|
||||||
|
if (!node.children || node.children.length === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.children
|
||||||
|
.map(child => this.processNode(child))
|
||||||
|
.filter(result => result !== '')
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template handlers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle fraction templates
|
||||||
|
*/
|
||||||
|
handleFraction(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const numerator = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
const denominator = children[1] ? this.processNode(children[1]) : '';
|
||||||
|
|
||||||
|
if (node.variation === 'tvFR_SLASH') {
|
||||||
|
return `<mfrac bevelled="true">${numerator}${denominator}</mfrac>`;
|
||||||
|
} else if (node.variation === 'tvFR_SMALL') {
|
||||||
|
return `<mstyle scriptlevel="+1"><mfrac>${numerator}${denominator}</mfrac></mstyle>`;
|
||||||
|
} else {
|
||||||
|
return `<mfrac>${numerator}${denominator}</mfrac>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle superscript templates
|
||||||
|
*/
|
||||||
|
handleSuperscript(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const base = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
const superscript = children[1] ? this.processNode(children[1]) : '';
|
||||||
|
|
||||||
|
if (node.variation === 'tvSU_PRECEDES') {
|
||||||
|
return `<mmultiscripts>${base}<mprescripts/><none/>${superscript}</mmultiscripts>`;
|
||||||
|
} else {
|
||||||
|
return `<msup>${base}${superscript}</msup>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle subscript templates
|
||||||
|
*/
|
||||||
|
handleSubscript(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const base = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
const subscript = children[1] ? this.processNode(children[1]) : '';
|
||||||
|
|
||||||
|
if (node.variation === 'tvSU_PRECEDES') {
|
||||||
|
return `<mmultiscripts>${base}<mprescripts/>${subscript}<none/></mmultiscripts>`;
|
||||||
|
} else {
|
||||||
|
return `<msub>${base}${subscript}</msub>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle combined subscript-superscript templates
|
||||||
|
*/
|
||||||
|
handleSubSuperscript(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const base = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
const subscript = children[1] ? this.processNode(children[1]) : '';
|
||||||
|
const superscript = children[2] ? this.processNode(children[2]) : '';
|
||||||
|
|
||||||
|
if (node.variation === 'tvSU_PRECEDES') {
|
||||||
|
return `<mmultiscripts>${base}<mprescripts/>${subscript}${superscript}</mmultiscripts>`;
|
||||||
|
} else {
|
||||||
|
return `<msubsup>${base}${subscript}${superscript}</msubsup>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle square root templates
|
||||||
|
*/
|
||||||
|
handleSquareRoot(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const content = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
return `<msqrt>${content}</msqrt>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle nth root templates
|
||||||
|
*/
|
||||||
|
handleRoot(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const radicand = children[0] ? this.processNode(children[0]) : '';
|
||||||
|
const index = children[1] ? this.processNode(children[1]) : '';
|
||||||
|
return `<mroot>${radicand}${index}</mroot>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle summation templates
|
||||||
|
*/
|
||||||
|
handleSummation(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const base = '<mo>∑</mo>';
|
||||||
|
|
||||||
|
if (children.length === 1) {
|
||||||
|
return `<mrow>${base}${this.processNode(children[0])}</mrow>`;
|
||||||
|
} else if (children.length === 2) {
|
||||||
|
return `<munder>${base}${this.processNode(children[0])}</munder>`;
|
||||||
|
} else if (children.length === 3) {
|
||||||
|
return `<munderover>${base}${this.processNode(children[1])}${this.processNode(children[2])}</munderover>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle integral templates
|
||||||
|
*/
|
||||||
|
handleIntegral(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
const base = '<mo>∫</mo>';
|
||||||
|
|
||||||
|
if (children.length === 1) {
|
||||||
|
return `<mrow>${base}${this.processNode(children[0])}</mrow>`;
|
||||||
|
} else if (children.length === 2) {
|
||||||
|
return `<munder>${base}${this.processNode(children[0])}</munder>`;
|
||||||
|
} else if (children.length === 3) {
|
||||||
|
return `<munderover>${base}${this.processNode(children[1])}${this.processNode(children[2])}</munderover>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle matrix templates
|
||||||
|
*/
|
||||||
|
handleMatrix(node) {
|
||||||
|
const children = node.children || [];
|
||||||
|
let tableContent = '';
|
||||||
|
|
||||||
|
children.forEach(row => {
|
||||||
|
if (row.type === 'mtr') {
|
||||||
|
tableContent += '<mtr>';
|
||||||
|
if (row.children) {
|
||||||
|
row.children.forEach(cell => {
|
||||||
|
if (cell.type === 'mtd') {
|
||||||
|
tableContent += `<mtd>${this.processNode(cell)}</mtd>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tableContent += '</mtr>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return `<mtable>${tableContent}</mtable>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle parentheses templates
|
||||||
|
*/
|
||||||
|
handleParentheses(node) {
|
||||||
|
const content = this.processChildren(node);
|
||||||
|
return `<mrow><mo>(</mo>${content}<mo>)</mo></mrow>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle bracket templates
|
||||||
|
*/
|
||||||
|
handleBrackets(node) {
|
||||||
|
const content = this.processChildren(node);
|
||||||
|
return `<mrow><mo>[</mo>${content}<mo>]</mo></mrow>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle brace templates
|
||||||
|
*/
|
||||||
|
handleBraces(node) {
|
||||||
|
const content = this.processChildren(node);
|
||||||
|
return `<mrow><mo>{</mo>${content}<mo>}</mo></mrow>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility functions for working with binary data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load binary file from input element
|
||||||
|
*/
|
||||||
|
function loadBinaryFile(fileInput) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
if (!file) {
|
||||||
|
reject(new Error('No file selected'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => resolve(e.target.result);
|
||||||
|
reader.onerror = (e) => reject(new Error('Failed to read file'));
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert ArrayBuffer to hex string for debugging
|
||||||
|
*/
|
||||||
|
function arrayBufferToHex(buffer) {
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
return Array.from(uint8Array)
|
||||||
|
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for Node.js or browser usage
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = {
|
||||||
|
MathTypeToMathMLConverter,
|
||||||
|
CharacterReplacer,
|
||||||
|
ElementMover,
|
||||||
|
MathMLTransformer,
|
||||||
|
loadBinaryFile,
|
||||||
|
arrayBufferToHex
|
||||||
|
};
|
||||||
|
} else if (typeof window !== 'undefined') {
|
||||||
|
window.MathTypeToMathMLConverter = MathTypeToMathMLConverter;
|
||||||
|
window.CharacterReplacer = CharacterReplacer;
|
||||||
|
window.ElementMover = ElementMover;
|
||||||
|
window.MathMLTransformer = MathMLTransformer;
|
||||||
|
window.loadBinaryFile = loadBinaryFile;
|
||||||
|
window.arrayBufferToHex = arrayBufferToHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example usage:
|
||||||
|
/*
|
||||||
|
// In a web page:
|
||||||
|
const converter = new MathTypeToMathMLConverter();
|
||||||
|
|
||||||
|
// From file input
|
||||||
|
document.getElementById('fileInput').addEventListener('change', async (e) => {
|
||||||
|
try {
|
||||||
|
const binaryData = await loadBinaryFile(e.target);
|
||||||
|
const mathml = await converter.convert(binaryData);
|
||||||
|
document.getElementById('output').innerHTML = mathml;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Conversion failed:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// From existing binary data
|
||||||
|
const binaryData = new Uint8Array([...]); // Your binary MTEF data
|
||||||
|
converter.convert(binaryData).then(mathml => {
|
||||||
|
console.log('Generated MathML:', mathml);
|
||||||
|
});
|
||||||
|
*/
|
||||||
387
scripts/mathtype2mathml/server_converter.js
Normal file
387
scripts/mathtype2mathml/server_converter.js
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
// Node.js server for MathType to MathML conversion
|
||||||
|
// This uses the actual Ruby gem to parse binary files
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const multer = require('multer');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const cors = require('cors');
|
||||||
|
const crypto = require('crypto'); // Added for filename generation
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3000;
|
||||||
|
|
||||||
|
// Define the correct paths relative to this file location
|
||||||
|
const MATHTYPE_DIR = __dirname; // Current directory is the mathtype_to_mathml folder
|
||||||
|
const CONVERTER_SCRIPT = path.join(MATHTYPE_DIR, 'server', 'converter.rb');
|
||||||
|
const BATCH_CONVERTER_SCRIPT = path.join(MATHTYPE_DIR, 'server', 'batch_converter.rb');
|
||||||
|
const PUBLIC_DIR = path.join(MATHTYPE_DIR, 'public');
|
||||||
|
|
||||||
|
// Enable CORS for cross-origin requests
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
// Configure multer for file uploads with proper extension preservation
|
||||||
|
const storage = multer.diskStorage({
|
||||||
|
destination: function (req, file, cb) {
|
||||||
|
cb(null, 'uploads/');
|
||||||
|
},
|
||||||
|
filename: function (req, file, cb) {
|
||||||
|
// Preserve the original file extension for proper format detection
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
const name = crypto.randomBytes(16).toString('hex');
|
||||||
|
cb(null, name + ext);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: storage,
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 } // 10MB limit
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve static files from the correct public directory
|
||||||
|
app.use(express.static(PUBLIC_DIR));
|
||||||
|
|
||||||
|
// Convert MathType binary to MathML using Ruby gem
|
||||||
|
app.post('/convert-mathtype', upload.single('file'), async (req, res) => {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: 'No file uploaded' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = req.file.path;
|
||||||
|
console.log(`File uploaded: ${req.file.originalname}`);
|
||||||
|
console.log(`Temp file path: ${filePath}`);
|
||||||
|
console.log(`File exists: ${fs.existsSync(filePath)}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Call Ruby script to convert the file
|
||||||
|
const mathml = await convertWithRubyGem(filePath);
|
||||||
|
|
||||||
|
// Clean up uploaded file
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
mathml: mathml,
|
||||||
|
originalName: req.file.originalname
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up uploaded file
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Conversion error:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Conversion failed',
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to call Ruby gem for conversion
|
||||||
|
function convertWithRubyGem(filePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Check if converter script exists
|
||||||
|
if (!fs.existsSync(CONVERTER_SCRIPT)) {
|
||||||
|
reject(new Error(`Converter script not found at: ${CONVERTER_SCRIPT}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert relative path to absolute path since Ruby script runs from different directory
|
||||||
|
const absoluteFilePath = path.resolve(filePath);
|
||||||
|
console.log(`Converting relative path: ${filePath}`);
|
||||||
|
console.log(`To absolute path: ${absoluteFilePath}`);
|
||||||
|
console.log(`Absolute file exists: ${fs.existsSync(absoluteFilePath)}`);
|
||||||
|
|
||||||
|
// Add debugging for working directory
|
||||||
|
console.log(`Working directory (MATHTYPE_DIR): ${MATHTYPE_DIR}`);
|
||||||
|
console.log(`Working directory exists: ${fs.existsSync(MATHTYPE_DIR)}`);
|
||||||
|
console.log(`Converter script path: ${CONVERTER_SCRIPT}`);
|
||||||
|
console.log(`Converter script exists: ${fs.existsSync(CONVERTER_SCRIPT)}`);
|
||||||
|
|
||||||
|
// Check if the file actually exists
|
||||||
|
if (!fs.existsSync(absoluteFilePath)) {
|
||||||
|
reject(new Error(`Upload file not found at: ${absoluteFilePath}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate working directory exists
|
||||||
|
if (!fs.existsSync(MATHTYPE_DIR)) {
|
||||||
|
reject(new Error(`Working directory not found at: ${MATHTYPE_DIR}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute Ruby script with bundle exec from the correct directory
|
||||||
|
// Removed shell: true to fix path issues with spaces
|
||||||
|
const ruby = spawn('bundle', ['exec', 'ruby', CONVERTER_SCRIPT, absoluteFilePath], {
|
||||||
|
cwd: MATHTYPE_DIR,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
let errorOutput = '';
|
||||||
|
|
||||||
|
ruby.stdout.on('data', (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.stderr.on('data', (data) => {
|
||||||
|
errorOutput += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.on('close', (code) => {
|
||||||
|
console.log(`Ruby process exited with code: ${code}`);
|
||||||
|
if (errorOutput) {
|
||||||
|
console.log(`Ruby stderr output: ${errorOutput}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
console.error(`Ruby script exited with code ${code}: ${errorOutput}`);
|
||||||
|
|
||||||
|
// Provide more specific error messages based on the error content
|
||||||
|
let errorMessage = 'Ruby conversion failed';
|
||||||
|
|
||||||
|
if (errorOutput.includes('undefined method')) {
|
||||||
|
errorMessage = 'MathType file parsing error - this may be due to an incompatible or corrupted MathType file format';
|
||||||
|
} else if (errorOutput.includes('No such file')) {
|
||||||
|
errorMessage = 'File not found during conversion';
|
||||||
|
} else if (errorOutput.includes('File is empty')) {
|
||||||
|
errorMessage = 'The uploaded file appears to be empty';
|
||||||
|
} else if (errorOutput.includes('Invalid file format')) {
|
||||||
|
errorMessage = 'The file does not appear to be a valid MathType binary file';
|
||||||
|
} else if (errorOutput.includes('Permission denied')) {
|
||||||
|
errorMessage = 'Permission error accessing the file';
|
||||||
|
} else if (errorOutput.includes('command not found') || errorOutput.includes('bundle: not found')) {
|
||||||
|
errorMessage = 'Bundle command not found - please ensure Ruby bundler is installed';
|
||||||
|
} else if (errorOutput.includes('Could not locate Gemfile')) {
|
||||||
|
errorMessage = 'Gemfile not found - please run bundle install from the project directory';
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error(`${errorMessage}: ${errorOutput || 'Unknown error'}`));
|
||||||
|
} else {
|
||||||
|
const mathML = output.trim();
|
||||||
|
if (!mathML) {
|
||||||
|
reject(new Error('Conversion resulted in empty MathML.'));
|
||||||
|
} else {
|
||||||
|
console.log('Ruby conversion successful, MathML length:', mathML.length);
|
||||||
|
resolve(mathML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.on('error', (err) => {
|
||||||
|
console.error('Failed to start Ruby process:', err);
|
||||||
|
console.error('Error details:', {
|
||||||
|
code: err.code,
|
||||||
|
errno: err.errno,
|
||||||
|
syscall: err.syscall,
|
||||||
|
path: err.path
|
||||||
|
});
|
||||||
|
|
||||||
|
let errorMessage = `Failed to start Ruby process: ${err.message}`;
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
errorMessage = 'Bundle command not found - please ensure Ruby bundler is installed and in your PATH';
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error(errorMessage));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health check endpoint
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
message: 'MathType converter server is running',
|
||||||
|
paths: {
|
||||||
|
mathtypeDir: MATHTYPE_DIR,
|
||||||
|
converterScript: CONVERTER_SCRIPT,
|
||||||
|
publicDir: PUBLIC_DIR
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Batch convert multiple MathType files to MathML
|
||||||
|
app.post('/convert-mathtype-batch', upload.array('files', 50), async (req, res) => {
|
||||||
|
if (!req.files || req.files.length === 0) {
|
||||||
|
return res.status(400).json({ error: 'No files uploaded' });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Batch conversion requested for ${req.files.length} files`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Prepare file paths for batch processing
|
||||||
|
const filePaths = req.files.map(file => path.resolve(file.path));
|
||||||
|
const fileInfo = req.files.map(file => ({
|
||||||
|
originalName: file.originalname,
|
||||||
|
tempPath: file.path,
|
||||||
|
size: file.size
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Files to process:', fileInfo.map(f => f.originalName));
|
||||||
|
|
||||||
|
// Call Ruby batch converter
|
||||||
|
const results = await convertBatchWithRubyGem(filePaths);
|
||||||
|
|
||||||
|
// Clean up uploaded files
|
||||||
|
req.files.forEach(file => {
|
||||||
|
if (fs.existsSync(file.path)) {
|
||||||
|
fs.unlinkSync(file.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine results with file info
|
||||||
|
const response = {
|
||||||
|
success: true,
|
||||||
|
totalFiles: req.files.length,
|
||||||
|
successful: results.successful,
|
||||||
|
failed: results.failed,
|
||||||
|
results: Object.entries(results.results).map(([filePath, result], index) => ({
|
||||||
|
originalName: fileInfo[index].originalName,
|
||||||
|
success: result.success,
|
||||||
|
mathml: result.mathml,
|
||||||
|
error: result.error,
|
||||||
|
fileSize: result.file_size || fileInfo[index].size
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(response);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up uploaded files in case of error
|
||||||
|
req.files.forEach(file => {
|
||||||
|
if (fs.existsSync(file.path)) {
|
||||||
|
fs.unlinkSync(file.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.error('Batch conversion error:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Batch conversion failed',
|
||||||
|
message: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to call Ruby batch converter
|
||||||
|
function convertBatchWithRubyGem(filePaths) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Check if batch converter script exists
|
||||||
|
if (!fs.existsSync(BATCH_CONVERTER_SCRIPT)) {
|
||||||
|
reject(new Error(`Batch converter script not found at: ${BATCH_CONVERTER_SCRIPT}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate working directory exists
|
||||||
|
if (!fs.existsSync(MATHTYPE_DIR)) {
|
||||||
|
reject(new Error(`Working directory not found at: ${MATHTYPE_DIR}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Starting batch conversion of ${filePaths.length} files`);
|
||||||
|
console.log(`Using batch converter: ${BATCH_CONVERTER_SCRIPT}`);
|
||||||
|
|
||||||
|
// Execute Ruby batch script
|
||||||
|
const args = ['exec', 'ruby', BATCH_CONVERTER_SCRIPT, ...filePaths];
|
||||||
|
const ruby = spawn('bundle', args, {
|
||||||
|
cwd: MATHTYPE_DIR,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
|
||||||
|
let output = '';
|
||||||
|
let errorOutput = '';
|
||||||
|
|
||||||
|
ruby.stdout.on('data', (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.stderr.on('data', (data) => {
|
||||||
|
errorOutput += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.on('close', (code) => {
|
||||||
|
console.log(`Ruby batch process exited with code: ${code}`);
|
||||||
|
if (errorOutput) {
|
||||||
|
console.log(`Ruby stderr output: ${errorOutput}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
console.error(`Ruby batch script exited with code ${code}: ${errorOutput}`);
|
||||||
|
reject(new Error(`Batch conversion failed: ${errorOutput || 'Unknown error'}`));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const results = JSON.parse(output.trim());
|
||||||
|
console.log(`Batch conversion completed: ${results.successful}/${results.total_files} successful`);
|
||||||
|
resolve(results);
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('Failed to parse batch conversion results:', parseError);
|
||||||
|
reject(new Error(`Failed to parse batch conversion results: ${parseError.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ruby.on('error', (err) => {
|
||||||
|
console.error('Failed to start Ruby batch process:', err);
|
||||||
|
let errorMessage = `Failed to start Ruby batch process: ${err.message}`;
|
||||||
|
if (err.code === 'ENOENT') {
|
||||||
|
errorMessage = 'Bundle command not found - please ensure Ruby bundler is installed and in your PATH';
|
||||||
|
}
|
||||||
|
reject(new Error(errorMessage));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set timeout for the entire batch (5 minutes)
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
ruby.kill('SIGTERM');
|
||||||
|
reject(new Error('Batch conversion timeout (5 minutes)'));
|
||||||
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
ruby.on('close', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostic endpoint for testing file uploads
|
||||||
|
app.post('/test-upload', upload.single('file'), (req, res) => {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: 'No file uploaded' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = req.file.path;
|
||||||
|
const absoluteFilePath = path.resolve(filePath);
|
||||||
|
|
||||||
|
// Get file info
|
||||||
|
const stats = fs.statSync(absoluteFilePath);
|
||||||
|
|
||||||
|
// Clean up uploaded file immediately after getting info
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
fileInfo: {
|
||||||
|
originalName: req.file.originalname,
|
||||||
|
size: stats.size,
|
||||||
|
mimetype: req.file.mimetype,
|
||||||
|
encoding: req.file.encoding,
|
||||||
|
tempPath: filePath,
|
||||||
|
absolutePath: absoluteFilePath,
|
||||||
|
existed: true
|
||||||
|
},
|
||||||
|
message: 'File upload test successful - file was received and cleaned up'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`MathType converter server running at http://localhost:${port}`);
|
||||||
|
console.log(`Using MathType directory: ${MATHTYPE_DIR}`);
|
||||||
|
console.log(`Converter script: ${CONVERTER_SCRIPT}`);
|
||||||
|
console.log(`Public directory: ${PUBLIC_DIR}`);
|
||||||
|
console.log('Make sure you have the mathtype_to_mathml Ruby gem installed:');
|
||||||
|
console.log(` cd "${MATHTYPE_DIR}" && bundle install`);
|
||||||
|
console.log(`Demo page available at: http://localhost:${port}/mathtype_converter_demo.html`);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
BIN
scripts/mathtype2mathml/test/input/25.bin
Normal file
BIN
scripts/mathtype2mathml/test/input/25.bin
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/test/input/26.bin
Normal file
BIN
scripts/mathtype2mathml/test/input/26.bin
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/test/input/40.bin
Normal file
BIN
scripts/mathtype2mathml/test/input/40.bin
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/test/input/61.bin
Normal file
BIN
scripts/mathtype2mathml/test/input/61.bin
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/test/output/25.wmf
Normal file
BIN
scripts/mathtype2mathml/test/output/25.wmf
Normal file
Binary file not shown.
BIN
scripts/mathtype2mathml/test/output/26.wmf
Normal file
BIN
scripts/mathtype2mathml/test/output/26.wmf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user