Java实现LaTeX转为OMML并写入word文档
1.需求
我有一些数学考题试卷数据存在数据库,考题数据是以大文本的形式存储,其中公式部分的格式为LaTeX,现在需要将这些数据生成word文档供用户下载,要求公式能够正确在文档中显示,一种方案是将LaTex转换成图片,然后写入word文档,缺点是清晰度不高,而且不可编辑。所以需要一个原生的解决方案,自然就想到word对LaTex的支持,遗憾的是word不支持LaTex, 而是有自己的格式OMML, 所以需要一个工具来将LaTex转换成OMML,现有的资料显示需要先将LaTex转换成MathML,然后将MathML转换成OMML。
2.代码结构

3.pom文件
<?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 https://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>2.5.8</version>
<relativePath/>
</parent>
<groupId>com.latex</groupId>
<artifactId>latex</artifactId>
<version>1.0.0</version>
<name>latex</name>
<description>latex</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 以下为转换和word操作需要的依赖 -->
<dependency>
<groupId>de.rototor.snuggletex</groupId>
<artifactId>snuggletex-core</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<!--
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>MML2OMML.XSL</include>
</includes>
</resource>
</resources>
-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</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>4.转换工具:LatexUtil
package com.latex.util;
import com.latex.enums.TextCircledEnum;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.officeDocument.x2006.math.CTOMath;
import org.openxmlformats.schemas.officeDocument.x2006.math.CTOMathPara;
import org.openxmlformats.schemas.officeDocument.x2006.math.CTR;
import uk.ac.ed.ph.snuggletex.SnuggleEngine;
import uk.ac.ed.ph.snuggletex.SnuggleInput;
import uk.ac.ed.ph.snuggletex.SnuggleSession;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
public class LatexUtil {
private static Transformer transformer;
private static final SnuggleEngine snuggleEngine = new SnuggleEngine();
/**
* 将latex公式添加到poi段落中
* @param paragraph 段落
* @param latex 公式
*/
public static void addToParagraph(XWPFParagraph paragraph, String latex) throws Exception{
latex = "$" + filter(latex) + "$"; //处理特殊符号
paragraph.getCTP().addNewOMath().set(getCTOMath(latex));
}
/**
* 将Latex公式转换成CTOMath(可直接写入word)
* @param latex 公式
* 这里的latex表达式 必须用$$包裹,例如:$\sin^2 \theta + \cos^2 \theta = 1$
*/
public static CTOMath getCTOMath(String latex) throws Exception {
System.out.println("Latex: " + latex);
String mathML = toMathML(latex);
System.out.println("MathML: " + mathML);
String OMML = toOMML(mathML);
System.out.println("OMML: " + OMML);
return toCTOMath(OMML);
}
/**
* latex转MathML
*/
public static String toMathML(String latex) throws IOException {
SnuggleSession session = snuggleEngine.createSession();
session.parseInput(new SnuggleInput(latex));
return session.buildXMLString();
}
/**
* MathML转OMML
*/
public static String toOMML(String mathML) throws TransformerException, IOException {
Transformer transformer = getTransformer();
StreamSource source = new StreamSource(new StringReader(mathML));
StringWriter stringwriter = new StringWriter();
StreamResult result = new StreamResult(stringwriter);
transformer.transform(source, result);
String OMML = stringwriter.toString();
stringwriter.close();
return OMML;
}
/**
* 基于OMML创建POI组件:CTOMath
*/
public static CTOMath toCTOMath(String OMML) throws XmlException {
CTOMathPara ctOMathPara = CTOMathPara.Factory.parse(OMML);
CTOMath ctOMath = ctOMathPara.getOMathArray(0);
//for making this to work with Office 2007 Word also, special font settings are necessary
XmlCursor xmlcursor = ctOMath.newCursor();
while (xmlcursor.hasNextToken()) {
XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
if (tokentype.isStart()) {
if (xmlcursor.getObject() instanceof CTR) {
CTR cTR = (CTR) xmlcursor.getObject();
cTR.addNewRPr2().addNewRFonts().setAscii("Cambria Math");
cTR.getRPr2().getRFonts().setHAnsi("Cambria Math"); // up to apache poi 4.1.2
//cTR.getRPr2().getRFontsArray(0).setHAnsi("Cambria Math"); // since apache poi 5.0.0
}
}
}
return ctOMath;
}
/**
* 发现存在无法识别的符号,因此单独处理,提前过滤识别掉
* ①②③④⑤等符合无法识别,即latex表达式是 \textcircled
* @param latex latex表达式
* @return 处理结果
*/
public static String filter(String latex){
if(!latex.contains("textcircled")){
return latex;
}
return TextCircledEnum.replaceTextCircled(latex);
}
private static Transformer getTransformer() throws TransformerException {
if(transformer == null){
//实测发现JDK21需要修改以下配置,不然newTransformer会报错:
//javax.xml.transform.TransformerConfigurationException: JAXP0801001
//System.setProperty("jdk.xml.xpathExprGrpLimit", "0"); // 0表示无限制
//System.setProperty("jdk.xml.xpathExprOpLimit", "0"); // 同时设置操作符限制
//如果读取不到MML2OMML.XSL文件,请检查pom文件
//build->resources->resource->includes下添加:<include>MML2OMML.XSL</include>
InputStream in = LatexUtil.class.getClassLoader().getResourceAsStream("MML2OMML.XSL");
TransformerFactory tFactory = TransformerFactory.newInstance();
transformer = tFactory.newTransformer(new StreamSource(in));
}
return transformer;
}
}
5.特殊符号处理:TextCircledEnum
package com.latex.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TextCircledEnum {
Zero("\\\\textcircled\\{0\\}","⓪"),
One("\\\\textcircled\\{1\\}","①"),
Two("\\\\textcircled\\{2\\}","②"),
Three("\\\\textcircled\\{3\\}","③"),
Four("\\\\textcircled\\{4\\}","④"),
Five("\\\\textcircled\\{5\\}","⑤"),
Six("\\\\textcircled\\{6\\}","⑥"),
Seven("\\\\textcircled\\{7\\}","⑦"),
Eight("\\\\textcircled\\{8\\}","⑧"),
Nine("\\\\textcircled\\{9\\}","⑨"),
Ten("\\\\textcircled\\{10\\}","⑩");
private final String code;
private final String value;
public static String replaceTextCircled(String latex){
for (TextCircledEnum c : values()) {
latex = latex.replaceAll(c.getCode(), c.getValue());
}
return latex;
}
}
6.测试类:LatexUtilTest
package com.latex.test;
import com.latex.util.LatexUtil;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import java.io.FileOutputStream;
public class LatexUtilTest {
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFParagraph paragraph = document.createParagraph();
paragraph.setAlignment(ParagraphAlignment.LEFT);
paragraph.setFontAlignment(ParagraphAlignment.LEFT.getValue());
paragraph.createRun().setText("前置文本:");
LatexUtil.addToParagraph(paragraph, "\\sin^2 \\theta + \\cos^2 \\theta = 1");
paragraph.createRun().setText("后置文本");
// 保存文档到文件
String filePath = "D:\\Test\\math_document_"+System.currentTimeMillis()+".docx";
try (FileOutputStream out = new FileOutputStream(filePath)) {
document.write(out);
System.out.println("Word文档已成功保存");
}
// 关闭文档
document.close();
}
}
7.测试输出
Latex: $\sin^2 \theta + \cos^2 \theta = 1$
MathML: <math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mi>sin</mi><mn>2</mn></msup><mi>θ</mi><mo>+</mo><msup><mi>cos</mi><mn>2</mn></msup><mi>θ</mi><mo>=</mo><mn>1</mn></math>
OMML: <?xml version="1.0" encoding="UTF-8"?><m:oMath xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mml="http://www.w3.org/1998/Math/MathML"><m:sSup><m:e><m:r><m:rPr><m:sty m:val="p"/></m:rPr><m:t>sin</m:t></m:r></m:e><m:sup><m:r><m:t>2</m:t></m:r></m:sup></m:sSup><m:r><m:t>θ</m:t></m:r><m:r><m:t>+</m:t></m:r><m:sSup><m:e><m:r><m:rPr><m:sty m:val="p"/></m:rPr><m:t>cos</m:t></m:r></m:e><m:sup><m:r><m:t>2</m:t></m:r></m:sup></m:sSup><m:r><m:t>θ</m:t></m:r><m:r><m:t>=</m:t></m:r><m:r><m:t>1</m:t></m:r></m:oMath>
Word文档已成功保存
8.Word文档内容

9.MML2OMML.XSL文件获取
MML2OMML.XSL文件的路径一般在C盘:C:\Program Files\Microsoft Office\root\Office16
到此这篇关于Java实现LaTeX转为OMML并写入word文档的文章就介绍到这了,更多相关Java LaTeX转OMML并写入word内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
详解spring cloud整合Swagger2构建RESTful服务的APIs
这篇文章主要介绍了详解spring cloud整合Swagger2构建RESTful服务的APIs,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧2018-01-01
一文详解JDK、Maven、Spring Boot各版本兼容性问题
Spring Boot 作为一个高度集成的框架,其版本兼容性涉及到多个方面,这篇文章主要介绍了JDK、Maven、Spring Boot各版本兼容性问题的相关资料,需要的朋友可以参考下2026-01-01


最新评论