java解析office数学公式

一、概述

公司的项目遇到一个需求是从office word中导入数据回显到页面当中,里面穿插着一些数学公式,又不想使用图片的方式存储数学公式。所以敲定了将公式转换成latex表达式,然后前端使用MathJax渲染出数学公式的方案。

1.1 所需资源
名称地址描述
mmltx.xslhttps://sourceforge.net/projects/xsltml/files/xsltml/MathML转LaTex工具
MML2OMML.XSLhttps://download.csdn.net/download/han949417140/18465121OMML转为MathML的xsl工具
1.2 office文档中的公式编辑器
  • office自带公式编辑器
    从2007版开始,Office也自带了一个公式编辑器。
    在2007版中Word与Excel之间不同的是,前者插入的公式对象是Office MathML节点,后者插入的还是ole。
    到了2010版开始,两个产品的公式编辑器插入的都是Office MathML节点了,但是两者对公式对象中的默认文字编码处理不同。
    这些不同点可以看出就算同样属于Office的产品,他们之间也是有很多不统一的地方。

  • 公式表达式
    LaTeX
    LaTeX是一种基于ΤΕΧ的排版系统,它非常适用于生成高印刷质量的科技和数学类文档。
    例如勾股定理用LaTeX表达:
    a 2 + b 2 = c 2 {a}^{2}+{b}^{2}={c}^{2} a2+b2=c2
    常用的LaTeX渲染组件是MathJax,
    vue渲染教程请参考:http://www.manongjc.com/detail/11-pwanxyhkqeiokqi.html
    我们在项目中使用的便是LaTeX,所以本次研究就是如何将Office中的公式对象转换成LaTeX表达式。

  • Mathml
    全称为数学标记语言(Mathematical Markup Language),是一种基于XML的标准,用来在互联网上书写数学符号和公式的置标语言。

  • Office MathML (OMML)
    在office2007之后版本所编辑的公式对象便是OMML。OMML是office为了配合Office Open Xml制定的数学标记语言。

1.3 转换关系

我们在项目中使用到的三者之间转换关系是:OMML -> MathML -> LaTex
Office在安装目录中提供了将OMML转为MathML的xsl工具:MML2OMML.XSL
MathML转LaTex使用网上找到另一个xsl工具mmltex.xsl

二、代码实现office公式解析

首先要说明我们的功能限制:只针对Office2011及以上的Office Open Xml文档,Word和Excel均可。
这里用Word文档为例子来说明解析过程。

2.1 引入pom包

pom…xml引入office接续所需jar

<dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.2.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.poi</groupId>
                    <artifactId>poi</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.poi</groupId>
                    <artifactId>poi-ooxml</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.poi</groupId>
                    <artifactId>poi-ooxml-schemas</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml-schemas -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/ooxml-schemas -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
2.2 导入解析所需工具

将上面下载的资源文件拷贝到项目resources/conventer下,如图:
在这里插入图片描述

2.3 编写代码解析office公式
package com.hanergy.out.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.springframework.web.multipart.MultipartFile;


import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * word内容段落、公式xml标签
 * 正常文本:
 * <w:r> 标签对应一个 XWPFRun对象
 * <w:t xml:space=“preserve”> 标签对应一段在 Word中的字符(也可以是一个字符)
 * 公式:
 * <w:object> 标签对应一个公式(当然我们这里只讲公式,此标签中也可以是一个 Excel也可以是一个 PPT等等)
 * <v:shape> 标签中有个 style属性,这里 style就是图片在 Word中显示的宽高
 * <v:imagedata> 标签关联着显示的图片( <v:imagedata>为 <v:shape>子标签)
 * <o:OLEObject>标签关联着图片显示公式对应的二进制文件(二进制文件也是最重要的文件,没有这个文件当你在word中双击时,是打不开第三方公式插件的)
 *
 */
/**
 * @description:
 * @author: Han LiDong
 * @create: 2021/5/7 10:33
 * @update: 2021/5/7 10:33
 */
@Slf4j
public class WordUtil {

    public static void main(String[] args) throws Exception {
//        MultipartFile file = null;
//        InputStream inputStream = file.getInputStream();
        // 所有公式latex表达式集合,借助mathJax可以在页面上进行展示
        List <String> formulas = getFormulaMap(new FileInputStream("C:\\D\\算法-β系数.docx"));
        log.info("解析到{}个公式",formulas.size());
        // word解析 公式+文本
        wordAnalysis(new FileInputStream("C:\\D\\算法-β系数.docx"));
    }

}

    /**
     * word所有内容解析(公式、文本)
     * @param inputStream
     * @throws Exception
     */
    public static void wordAnalysis(InputStream inputStream) throws Exception {
        XWPFDocument word = new XWPFDocument(inputStream);
        try {
            for (IBodyElement ibodyelement : word.getBodyElements()) {
                if (ibodyelement.getElementType().equals(BodyElementType.PARAGRAPH)) {  //段落
                    XWPFParagraph paragraph = (XWPFParagraph) ibodyelement;
                    //段落解析
                    String paragraphStr = parseParagraph(paragraph);
                } else if (ibodyelement.getElementType().equals(BodyElementType.TABLE)) {   //表格
                    XWPFTable table = (XWPFTable) ibodyelement;
                    for (XWPFTableRow row : table.getRows()) {  //行
                        for (XWPFTableCell cell : row.getTableCells()) {    //cell
                            List<String> cellMath = new ArrayList<>(16);
                            for (XWPFParagraph paragraph : cell.getParagraphs()) {  //段落
                                //cell段落解析
                                String paragraphStr = parseParagraph(paragraph);
                                if (!"".equals(paragraphStr.trim())){
                                    cellMath.add(paragraphStr);
                                }
                            }
                            log.info("当前cell有{}个公式",cellMath.size());
                        }
                    }
                }
            }

        } finally {
            word.close();
        }
    }


    /**
     * 解析word中公式(转换成latex表达式)
     *
     * @param inputStream  文件流
     * @return
     */
    public static List<String> getFormulaMap(InputStream inputStream) throws IOException, DocumentException {
        //XWPFDocument xwpfDocument = new XWPFDocument(inputStream);
        Map<Integer, String> result = new HashMap<>();
        XWPFDocument word = new XWPFDocument(inputStream);
        //storing the found MathML in a AllayList of strings
        List<String> mathMLList = new ArrayList<String>(16);
        try {
            for (IBodyElement ibodyelement : word.getBodyElements()) {
                if (ibodyelement.getElementType().equals(BodyElementType.PARAGRAPH)) {  //段落
                    XWPFParagraph paragraph = (XWPFParagraph) ibodyelement;
                    //段落解析
                    List<String> mathList = parseMathParagraph(paragraph);
                    mathMLList.addAll(mathList);
                } else if (ibodyelement.getElementType().equals(BodyElementType.TABLE)) { //woed表格
                    XWPFTable table = (XWPFTable) ibodyelement;
                    for (XWPFTableRow row : table.getRows()) {
                        for (XWPFTableCell cell : row.getTableCells()) {
                            List<String> cellMath = new ArrayList<>(16);
                            for (XWPFParagraph paragraph : cell.getParagraphs()) {
                                //cell段落解析
                                List<String> mathList = parseMathParagraph(paragraph);
                                mathMLList.addAll(mathList);
                                //cellMath.addAll(mathList);
                            }
                        }
                    }
                }
            }
        } finally {
            word.close();
        }
        log.info("当前文档一共有{}个公式",mathMLList.size());
        return mathMLList;
    }

    /**
     * 公式段落解析
     * @param xwpfParagraph
     * @throws DocumentException
     */
    public static List<String> parseMathParagraph(XWPFParagraph xwpfParagraph) throws DocumentException {
        CTP ctp = xwpfParagraph.getCTP();
        String xmlText = ctp.xmlText();
        List<String > mathList = new ArrayList<>();
        if (xmlText.contains("<m:oMath>")) {
            //得到根节点的值
            SAXReader saxReader = new SAXReader();
            //将String类型的字符串转换成XML文本对象
            Document doc = saxReader.read(new ByteArrayInputStream(xmlText.getBytes()));
            Element root = doc.getRootElement();
            // 一个段落多个表达式解析
            List<Element> omMaths = root.selectNodes("//m:oMath");    //用xpath得到OMML节点
            for (Element ele : omMaths) {
                /**
                 * OMML -> MathML -> LaTex
                 * Office在安装目录中提供了将OMML转为MathML的xsl工具:MML2OMML.XSL
                 * MathML转LaTex使用网上找到另一个xsl工具mmltex.xsl。
                 */
                String xml = ele.asXML();
                //xml转 mathml
                String mml = convertOMML2MML(xml);
                //mathml转latx
                String latex = convertMML2Latex(mml);
                mathList.add(latex);
                log.info("late表达式:{}" , latex);
            }
        }
        return mathList;
    }

    /**
     * 段落解析
     * @param xwpfParagraph
     * @throws DocumentException
     */
    public static String parseParagraph(XWPFParagraph xwpfParagraph) throws DocumentException {
        CTP ctp = xwpfParagraph.getCTP();
        String xmlText = ctp.xmlText();
        StringBuilder sb = new StringBuilder();
//        if (xmlText.contains("<m:oMath>")) {

        //段落文本内容
        sb.append(xwpfParagraph.getParagraphText());
        //段落公式解析
        //得到根节点的值
        SAXReader saxReader = new SAXReader();
        //将String类型的字符串转换成XML文本对象
        Document doc = saxReader.read(new ByteArrayInputStream(xmlText.getBytes()));
        Element root = doc.getRootElement();
        // 一个段落多个表达式解析
        List<Element> omMaths = root.selectNodes("//m:oMath");    //用xpath得到OMML节点
        if (omMaths != null && !omMaths.isEmpty()) {
            for (Element ele : omMaths) {
                /**
                 * OMML -> MathML -> LaTex
                 * Office在安装目录中提供了将OMML转为MathML的xsl工具:MML2OMML.XSL
                 * MathML转LaTex使用网上找到另一个xsl工具mmltex.xsl。
                 */
                String xml = ele.asXML();
                //xml转 mathml
                String mathml = convertOMML2MML(xml);
                //mathml转latx
                String latex = convertMML2Latex(mathml);
                sb.append(latex);
                log.info("latex表达式:{}",latex);
            }
        }
        log.info("公式个数:{},解析内容:{}",omMaths.size(),sb.toString());
        return sb.toString();
    }

    /**
     * Description: xsl转换器</p>
     * @param s  公式xml字符串
     * @param xslpath   转换器路径
     * @param uriResolver xls依赖文件
     * @return
     */
    public static String xslConvert(String s, String xslpath, URIResolver uriResolver){
        TransformerFactory tFac = TransformerFactory.newInstance();
        if(uriResolver != null) {
            tFac.setURIResolver(uriResolver);
        }
        StreamSource xslSource = new StreamSource(WordUtil.class.getResourceAsStream(xslpath));
        StringWriter writer = new StringWriter();
        try {
            Transformer t = tFac.newTransformer(xslSource);
            Source source = new StreamSource(new StringReader(s));
            Result result = new StreamResult(writer);
            t.transform(source, result);
        } catch (TransformerException e) {
            log.error(e.getMessage(), e);
        }
        return writer.getBuffer().toString();
    }

    /**
     * <p>Description: 将mathml转为latx </p>
     * @param mml mathml字符串
     * @return
     */
    public static String convertMML2Latex(String mml){
        mml = mml.substring(mml.indexOf("?>")+2, mml.length()); //去掉xml的头节点
        URIResolver r = new URIResolver(){  //设置xls依赖文件的路径
            @Override
            public Source resolve(String href, String base) throws TransformerException {
                File f = new File("/conventer/mml2tex/" + href);
                InputStream inputStream = WordUtil.class.getResourceAsStream("/conventer/mml2tex/" + href);
                return new StreamSource(inputStream);
            }
        };
        String latex = xslConvert(mml, "/conventer/mml2tex/mmltex.xsl", r);
        if(latex != null && latex.length() > 1){
            latex = latex.substring(1, latex.length() - 1);
        }
        return latex;
    }
    /**
     * <p>Description: office xml转为mathml </p>
     * @param xml  公式xml
     * @return
     */
    public static String convertOMML2MML(String xml){
        // 进行转换的过程中需要借助这个文件,一般来说本机安装office就会有这个文件,找到就可以
        String result = xslConvert(xml, "/conventer/OMML2MML.XSL", null);
        return result;
    }
  • 最终运行结果如图:

在这里插入图片描述

  • word文档示例
    在这里插入图片描述
  • 在线解析latex表达式
    从程序运行果果中拷贝一个latex表达式在线解析:x=\frac{-b\pm \sqrt{{b}^{2}-4ac}}{2a}
    在线解析网址:https://latex.91maths.com/

在这里插入图片描述

参考博客:
https://blog.csdn.net/qq_41536778/article/details/108454415
https://www.cnblogs.com/wadmwz/p/10460033.html

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值